/* ******************************************************************* * 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 * ******************************************************************/ package org.aspectj.weaver.bcel; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.Set; import org.aspectj.apache.bcel.Constants; import org.aspectj.apache.bcel.classfile.BootstrapMethods; import org.aspectj.apache.bcel.classfile.ConstantPool; import org.aspectj.apache.bcel.classfile.Method; import org.aspectj.apache.bcel.classfile.annotation.AnnotationGen; import org.aspectj.apache.bcel.generic.FieldGen; import org.aspectj.apache.bcel.generic.FieldInstruction; import org.aspectj.apache.bcel.generic.Instruction; import org.aspectj.apache.bcel.generic.InstructionBranch; import org.aspectj.apache.bcel.generic.InstructionCP; 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.InstructionSelect; 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.MethodGen; import org.aspectj.apache.bcel.generic.ObjectType; import org.aspectj.apache.bcel.generic.RET; import org.aspectj.apache.bcel.generic.Tag; import org.aspectj.apache.bcel.generic.Type; import org.aspectj.asm.AsmManager; import org.aspectj.bridge.IMessage; import org.aspectj.bridge.ISourceLocation; import org.aspectj.bridge.Message; import org.aspectj.bridge.MessageUtil; import org.aspectj.bridge.WeaveMessage; import org.aspectj.bridge.context.CompilationAndWeavingContext; import org.aspectj.bridge.context.ContextToken; import org.aspectj.util.PartialOrder; import org.aspectj.weaver.AjAttribute; import org.aspectj.weaver.AjcMemberMaker; import org.aspectj.weaver.AnnotationAJ; import org.aspectj.weaver.BCException; import org.aspectj.weaver.ConcreteTypeMunger; import org.aspectj.weaver.IClassWeaver; import org.aspectj.weaver.IntMap; import org.aspectj.weaver.Member; import org.aspectj.weaver.MissingResolvedTypeWithKnownSignature; 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.ResolvedTypeMunger; import org.aspectj.weaver.Shadow; import org.aspectj.weaver.ShadowMunger; import org.aspectj.weaver.UnresolvedType; import org.aspectj.weaver.UnresolvedTypeVariableReferenceType; import org.aspectj.weaver.WeaverStateInfo; import org.aspectj.weaver.World; import org.aspectj.weaver.model.AsmRelationshipProvider; import org.aspectj.weaver.patterns.DeclareAnnotation; import org.aspectj.weaver.patterns.ExactTypePattern; import org.aspectj.weaver.tools.Trace; import org.aspectj.weaver.tools.TraceFactory; class BcelClassWeaver implements IClassWeaver { private static Trace trace = TraceFactory.getTraceFactory().getTrace(BcelClassWeaver.class); // Name of helper method generated by JDT compiler. Javac uses a separate inner class. private static final String SWITCH_TABLE_SYNTHETIC_METHOD_PREFIX = "$SWITCH_TABLE$"; public static boolean weave(BcelWorld world, LazyClassGen clazz, List shadowMungers, List typeMungers, List lateTypeMungers, boolean inReweavableMode) { BcelClassWeaver classWeaver = new BcelClassWeaver(world, clazz, shadowMungers, typeMungers, lateTypeMungers); classWeaver.setReweavableMode(inReweavableMode); boolean b = classWeaver.weave(); return b; } // -------------------------------------------- private final LazyClassGen clazz; private final List shadowMungers; private final List typeMungers; private final List lateTypeMungers; private List[] indexedShadowMungers; private boolean canMatchBodyShadows = false; private final BcelObjectType ty; // alias of clazz.getType() private final BcelWorld world; // alias of ty.getWorld() private final ConstantPool cpg; // alias of clazz.getConstantPoolGen() private final InstructionFactory fact; // alias of clazz.getFactory(); private final List addedLazyMethodGens = new ArrayList<>(); private final Set addedDispatchTargets = new HashSet<>(); private boolean inReweavableMode = false; private List addedSuperInitializersAsList = null; private final Map addedSuperInitializers = new HashMap<>(); private final List addedThisInitializers = new ArrayList<>(); private final List addedClassInitializers = new ArrayList<>(); private final Map mapToAnnotationHolder = new HashMap<>(); // private BcelShadow clinitShadow = null; /** * This holds the initialization and pre-initialization shadows for this class that were actually matched by mungers (if no * match, then we don't even create the shadows really). */ private final List initializationShadows = new ArrayList<>(); private BcelClassWeaver(BcelWorld world, LazyClassGen clazz, List shadowMungers, List typeMungers, List lateTypeMungers) { super(); this.world = world; this.clazz = clazz; this.shadowMungers = shadowMungers; this.typeMungers = typeMungers; this.lateTypeMungers = lateTypeMungers; this.ty = clazz.getBcelObjectType(); this.cpg = clazz.getConstantPool(); this.fact = clazz.getFactory(); indexShadowMungers(); initializeSuperInitializerMap(ty.getResolvedTypeX()); if (!checkedXsetForLowLevelContextCapturing) { Properties p = world.getExtraConfiguration(); if (p != null) { String s = p.getProperty(World.xsetCAPTURE_ALL_CONTEXT, "false"); captureLowLevelContext = s.equalsIgnoreCase("true"); if (captureLowLevelContext) { world.getMessageHandler().handleMessage( MessageUtil.info("[" + World.xsetCAPTURE_ALL_CONTEXT + "=true] Enabling collection of low level context for debug/crash messages")); } } checkedXsetForLowLevelContextCapturing = true; } } private boolean canMatch(Shadow.Kind kind) { return indexedShadowMungers[kind.getKey()] != null; } // private void fastMatchShadowMungers(List shadowMungers, ArrayList // mungers, Kind kind) { // FastMatchInfo info = new FastMatchInfo(clazz.getType(), kind); // for (Iterator i = shadowMungers.iterator(); i.hasNext();) { // ShadowMunger munger = (ShadowMunger) i.next(); // FuzzyBoolean fb = munger.getPointcut().fastMatch(info); // WeaverMetrics.recordFastMatchResult(fb);// Could pass: // munger.getPointcut().toString() // if (fb.maybeTrue()) mungers.add(munger); // } // } private void initializeSuperInitializerMap(ResolvedType child) { ResolvedType[] superInterfaces = child.getDeclaredInterfaces(); for (ResolvedType superInterface : superInterfaces) { if (ty.getResolvedTypeX().isTopmostImplementor(superInterface)) { if (addSuperInitializer(superInterface)) { initializeSuperInitializerMap(superInterface); } } } } /** * Process the shadow mungers into array 'buckets', each bucket represents a shadow kind and contains a list of shadowmungers * that could potentially apply at that shadow kind. */ private void indexShadowMungers() { // beware the annoying property that SHADOW_KINDS[i].getKey == (i+1) ! indexedShadowMungers = new List[Shadow.MAX_SHADOW_KIND + 1]; for (ShadowMunger shadowMunger : shadowMungers) { int couldMatchKinds = shadowMunger.getPointcut().couldMatchKinds(); for (Shadow.Kind kind : Shadow.SHADOW_KINDS) { if (kind.isSet(couldMatchKinds)) { byte k = kind.getKey(); if (indexedShadowMungers[k] == null) { indexedShadowMungers[k] = new ArrayList<>(); if (!kind.isEnclosingKind()) { canMatchBodyShadows = true; } } indexedShadowMungers[k].add(shadowMunger); } } } } private boolean addSuperInitializer(ResolvedType onType) { if (onType.isRawType() || onType.isParameterizedType()) { onType = onType.getGenericType(); } IfaceInitList l = addedSuperInitializers.get(onType); if (l != null) { return false; } l = new IfaceInitList(onType); addedSuperInitializers.put(onType, l); return true; } public void addInitializer(ConcreteTypeMunger cm) { NewFieldTypeMunger m = (NewFieldTypeMunger) cm.getMunger(); ResolvedType onType = m.getSignature().getDeclaringType().resolve(world); if (onType.isRawType()) { onType = onType.getGenericType(); } if (Modifier.isStatic(m.getSignature().getModifiers())) { addedClassInitializers.add(cm); } else { if (onType == ty.getResolvedTypeX()) { addedThisInitializers.add(cm); } else { IfaceInitList l = addedSuperInitializers.get(onType); l.list.add(cm); } } } private static class IfaceInitList implements PartialOrder.PartialComparable { final ResolvedType onType; List list = new ArrayList<>(); IfaceInitList(ResolvedType onType) { this.onType = onType; } public int compareTo(Object other) { IfaceInitList o = (IfaceInitList) other; if (onType.isAssignableFrom(o.onType)) { return +1; } else if (o.onType.isAssignableFrom(onType)) { return -1; } else { return 0; } } public int fallbackCompareTo(Object other) { return 0; } } // XXX this is being called, but the result doesn't seem to be being used public boolean addDispatchTarget(ResolvedMember m) { return addedDispatchTargets.add(m); } public void addLazyMethodGen(LazyMethodGen gen) { addedLazyMethodGens.add(gen); } public void addOrReplaceLazyMethodGen(LazyMethodGen mg) { if (alreadyDefined(clazz, mg)) { return; } for (Iterator i = addedLazyMethodGens.iterator(); i.hasNext();) { LazyMethodGen existing = i.next(); if (signaturesMatch(mg, existing)) { if (existing.definingType == null) { // this means existing was introduced on the class itself return; } else if (mg.definingType.isAssignableFrom(existing.definingType)) { // existing is mg's subtype and dominates mg return; } else if (existing.definingType.isAssignableFrom(mg.definingType)) { // mg is existing's subtype and dominates existing i.remove(); addedLazyMethodGens.add(mg); return; } else { throw new BCException("conflict between: " + mg + " and " + existing); } } } addedLazyMethodGens.add(mg); } private boolean alreadyDefined(LazyClassGen clazz, LazyMethodGen mg) { for (Iterator i = clazz.getMethodGens().iterator(); i.hasNext();) { LazyMethodGen existing = i.next(); if (signaturesMatch(mg, existing)) { if (!mg.isAbstract() && existing.isAbstract()) { i.remove(); return false; } return true; } } return false; } private boolean signaturesMatch(LazyMethodGen mg, LazyMethodGen existing) { return mg.getName().equals(existing.getName()) && mg.getSignature().equals(existing.getSignature()); } protected static LazyMethodGen makeBridgeMethod(LazyClassGen gen, ResolvedMember member) { // remove abstract modifier int mods = member.getModifiers(); if (Modifier.isAbstract(mods)) { mods = mods - Modifier.ABSTRACT; } LazyMethodGen ret = new LazyMethodGen(mods, BcelWorld.makeBcelType(member.getReturnType()), member.getName(), BcelWorld.makeBcelTypes(member.getParameterTypes()), UnresolvedType.getNames(member.getExceptions()), gen); // 43972 : Static crosscutting makes interfaces unusable for javac // ret.makeSynthetic(); return ret; } /** * Create a single bridge method called 'theBridgeMethod' that bridges to 'whatToBridgeTo' */ private static void createBridgeMethod(BcelWorld world, LazyMethodGen whatToBridgeToMethodGen, LazyClassGen clazz, ResolvedMember theBridgeMethod) { InstructionList body; InstructionFactory fact; int pos = 0; ResolvedMember whatToBridgeTo = whatToBridgeToMethodGen.getMemberView(); if (whatToBridgeTo == null) { whatToBridgeTo = new ResolvedMemberImpl(Member.METHOD, whatToBridgeToMethodGen.getEnclosingClass().getType(), whatToBridgeToMethodGen.getAccessFlags(), whatToBridgeToMethodGen.getName(), whatToBridgeToMethodGen.getSignature()); } // The bridge method in this type will have the same signature as the one in the supertype LazyMethodGen bridgeMethod = makeBridgeMethod(clazz, theBridgeMethod); int newflags = bridgeMethod.getAccessFlags() | Constants.ACC_BRIDGE | Constants.ACC_SYNTHETIC ;// BRIDGE = 0x00000040 if ((newflags & 0x00000100) != 0) { newflags = newflags - 0x100;// NATIVE = 0x00000100 - need to clear it } bridgeMethod.setAccessFlags(newflags); Type returnType = BcelWorld.makeBcelType(theBridgeMethod.getReturnType()); Type[] paramTypes = BcelWorld.makeBcelTypes(theBridgeMethod.getParameterTypes()); Type[] newParamTypes = whatToBridgeToMethodGen.getArgumentTypes(); body = bridgeMethod.getBody(); fact = clazz.getFactory(); if (!whatToBridgeToMethodGen.isStatic()) { body.append(InstructionFactory.createThis()); pos++; } for (int i = 0, len = paramTypes.length; i < len; i++) { Type paramType = paramTypes[i]; body.append(InstructionFactory.createLoad(paramType, pos)); if (!newParamTypes[i].equals(paramTypes[i])) { if (world.forDEBUG_bridgingCode) { System.err.println("Bridging: Cast " + newParamTypes[i] + " from " + paramTypes[i]); } body.append(fact.createCast(paramTypes[i], newParamTypes[i])); } pos += paramType.getSize(); } body.append(Utility.createInvoke(fact, world, whatToBridgeTo)); body.append(InstructionFactory.createReturn(returnType)); clazz.addMethodGen(bridgeMethod); } /** * Weave a class and indicate through the return value whether the class was modified. * * @return true if the class was modified */ public boolean weave() { if (clazz.isWoven() && !clazz.isReweavable()) { if (world.getLint().nonReweavableTypeEncountered.isEnabled()) { world.getLint().nonReweavableTypeEncountered.signal(clazz.getType().getName(), ty.getSourceLocation()); } // Integer uniqueID = new Integer(rm.hashCode() * deca.hashCode()); // if (!reportedProblems.contains(uniqueID)) { // reportedProblems.add(uniqueID); // world.getLint().elementAlreadyAnnotated.signal(new String[] { rm.toString(), // world.showMessage(IMessage.ERROR, WeaverMessages.format(WeaverMessages.ALREADY_WOVEN, clazz.getType().getName()), // ty.getSourceLocation(), null); return false; } Set aspectsAffectingType = null; if (inReweavableMode || clazz.getType().isAspect()) { aspectsAffectingType = new HashSet<>(); } boolean isChanged = false; // we want to "touch" all aspects if (clazz.getType().isAspect()) { isChanged = true; } WeaverStateInfo typeWeaverState = (world.isOverWeaving() ? getLazyClassGen().getType().getWeaverState() : null); // start by munging all typeMungers for (ConcreteTypeMunger o : typeMungers) { if (!(o instanceof BcelTypeMunger)) { // ???System.err.println("surprising: " + o); continue; } BcelTypeMunger munger = (BcelTypeMunger) o; if (typeWeaverState != null && typeWeaverState.isAspectAlreadyApplied(munger.getAspectType())) { continue; } boolean typeMungerAffectedType = munger.munge(this); if (typeMungerAffectedType) { isChanged = true; if (inReweavableMode || clazz.getType().isAspect()) { aspectsAffectingType.add(munger.getAspectType().getSignature()); } } } // Weave special half type/half shadow mungers... isChanged = weaveDeclareAtMethodCtor(clazz) || isChanged; isChanged = weaveDeclareAtField(clazz) || isChanged; // XXX do major sort of stuff // sort according to: Major: type hierarchy // within each list: dominates // don't forget to sort addedThisInitialiers according to dominates addedSuperInitializersAsList = new ArrayList<>(addedSuperInitializers.values()); addedSuperInitializersAsList = PartialOrder.sort(addedSuperInitializersAsList); if (addedSuperInitializersAsList == null) { throw new BCException("circularity in inter-types"); } // this will create a static initializer if there isn't one // this is in just as bad taste as NOPs LazyMethodGen staticInit = clazz.getStaticInitializer(); staticInit.getBody().insert(genInitInstructions(addedClassInitializers, true)); // now go through each method, and match against each method. This // sets up each method's {@link LazyMethodGen#matchedShadows} field, // and it also possibly adds to {@link #initializationShadows}. List methodGens = new ArrayList<>(clazz.getMethodGens()); for (LazyMethodGen member : methodGens) { if (!member.hasBody()) { continue; } if (world.isJoinpointSynchronizationEnabled() && world.areSynchronizationPointcutsInUse() && member.getMethod().isSynchronized()) { transformSynchronizedMethod(member); } boolean shadowMungerMatched = match(member); if (shadowMungerMatched) { // For matching mungers, add their declaring aspects to the list // that affected this type if (inReweavableMode || clazz.getType().isAspect()) { aspectsAffectingType.addAll(findAspectsForMungers(member)); } isChanged = true; } } // now we weave all but the initialization shadows for (LazyMethodGen methodGen : methodGens) { if (!methodGen.hasBody()) { continue; } implement(methodGen); } // if we matched any initialization shadows, we inline and weave if (!initializationShadows.isEmpty()) { // Repeat next step until nothing left to inline...cant go on // infinetly as compiler will have detected and reported // "Recursive constructor invocation" List recursiveCtors = new ArrayList<>(); while (inlineSelfConstructors(methodGens, recursiveCtors)) { } positionAndImplement(initializationShadows); } // now proceed with late type mungers if (lateTypeMungers != null) { for (ConcreteTypeMunger lateTypeMunger : lateTypeMungers) { BcelTypeMunger munger = (BcelTypeMunger) lateTypeMunger; if (munger.matches(clazz.getType())) { boolean typeMungerAffectedType = munger.munge(this); if (typeMungerAffectedType) { isChanged = true; if (inReweavableMode || clazz.getType().isAspect()) { aspectsAffectingType.add(munger.getAspectType().getSignature()); } } } } } // FIXME AV - see #75442, for now this is not enough to fix the bug, // comment that out until we really fix it // // flush to save some memory // PerObjectInterfaceTypeMunger.unregisterFromAsAdvisedBy(clazz.getType() // ); // finally, if we changed, we add in the introduced methods. if (isChanged) { clazz.getOrCreateWeaverStateInfo(inReweavableMode); weaveInAddedMethods(); } if (inReweavableMode) { WeaverStateInfo wsi = clazz.getOrCreateWeaverStateInfo(true); wsi.addAspectsAffectingType(aspectsAffectingType); if (!world.isOverWeaving()) { wsi.setUnwovenClassFileData(ty.getJavaClass().getBytes()); wsi.setReweavable(true); } else { wsi.markOverweavingInUse(); } } else { clazz.getOrCreateWeaverStateInfo(false).setReweavable(false); } // tidyup, reduce ongoing memory usage of BcelMethods that hang around for (LazyMethodGen mg : methodGens) { BcelMethod method = mg.getMemberView(); if (method != null) { method.wipeJoinpointSignatures(); } } return isChanged; } // **************************** start of bridge method creation code // ***************** // FIXASC tidy this lot up !! // FIXASC refactor into ResolvedType or even ResolvedMember? /** * Check if a particular method is overriding another - refactored into this helper so it can be used from multiple places. * @return method that is overriding if it */ private static ResolvedMember isOverriding(ResolvedType typeToCheck, ResolvedMember methodThatMightBeGettingOverridden, String mname, String mrettype, int mmods, boolean inSamePackage, UnresolvedType[] methodParamsArray) { // Check if we can be an override... if (Modifier.isStatic(methodThatMightBeGettingOverridden.getModifiers())) { // we can't be overriding a static method return null; } if (Modifier.isPrivate(methodThatMightBeGettingOverridden.getModifiers())) { // we can't be overriding a private method return null; } if (!methodThatMightBeGettingOverridden.getName().equals(mname)) { // names do not match (this will also skip and ) return null; } if (methodThatMightBeGettingOverridden.getParameterTypes().length != methodParamsArray.length) { // not the same number of parameters return null; } if (!isVisibilityOverride(mmods, methodThatMightBeGettingOverridden, inSamePackage)) { // not override from visibility point of view return null; } if (typeToCheck.getWorld().forDEBUG_bridgingCode) { System.err.println(" Bridging:seriously considering this might be getting overridden '" + methodThatMightBeGettingOverridden + "'"); } World w = typeToCheck.getWorld(); // Look at erasures of parameters (List erased is List) boolean sameParams = true; for (int p = 0, max = methodThatMightBeGettingOverridden.getParameterTypes().length; p < max; p++) { UnresolvedType mtmbgoParameter = methodThatMightBeGettingOverridden.getParameterTypes()[p]; UnresolvedType ptype = methodParamsArray[p]; if (mtmbgoParameter.isTypeVariableReference()) { if (!mtmbgoParameter.resolve(w).isAssignableFrom(ptype.resolve(w))) { sameParams = false; } } else { // old condition: boolean b = !methodThatMightBeGettingOverridden.getParameterTypes()[p].getErasureSignature().equals( methodParamsArray[p].getErasureSignature()); UnresolvedType parameterType = methodThatMightBeGettingOverridden.getParameterTypes()[p]; // Collapse to first bound (isn't that the same as erasure! if (parameterType instanceof UnresolvedTypeVariableReferenceType) { parameterType = ((UnresolvedTypeVariableReferenceType) parameterType).getTypeVariable().getFirstBound(); } if (b) { // !parameterType.resolve(w).equals(parameterType2.resolve(w))) { sameParams = false; } } // // if (!ut.getErasureSignature().equals(ut2.getErasureSignature())) // sameParams = false; } // If the 'typeToCheck' represents a parameterized type then the method // will be the parameterized form of the // generic method in the generic type. So if the method was 'void // m(List lt, T t)' and the parameterized type here // is I then the method we are looking at will be 'void // m(List lt, String t)' which when erased // is 'void m(List lt,String t)' - so if the parameters *do* match then // there is a generic method we are // overriding // FIXASC Why bother with the return type? If it is incompatible then the code has other problems! if (sameParams) { if (typeToCheck.isParameterizedType()) { return methodThatMightBeGettingOverridden.getBackingGenericMember(); } else if (!methodThatMightBeGettingOverridden.getReturnType().getErasureSignature().equals(mrettype)) { // addressing the wierd situation from bug 147801 // just check whether these things are in the right relationship // for covariance... ResolvedType superReturn = typeToCheck.getWorld().resolve( UnresolvedType.forSignature(methodThatMightBeGettingOverridden.getReturnType().getErasureSignature())); ResolvedType subReturn = typeToCheck.getWorld().resolve(UnresolvedType.forSignature(mrettype)); if (superReturn.isAssignableFrom(subReturn)) { return methodThatMightBeGettingOverridden; } // } else if (typeToCheck.isParameterizedType()) { // return methodThatMightBeGettingOverridden.getBackingGenericMember(); } else { return methodThatMightBeGettingOverridden; } } return null; } /** * Looks at the visibility modifiers between two methods, and knows whether they are from classes in the same package, and * decides whether one overrides the other. * * @return true if there is an overrides rather than a 'hides' relationship */ static boolean isVisibilityOverride(int methodMods, ResolvedMember inheritedMethod, boolean inSamePackage) { int inheritedModifiers = inheritedMethod.getModifiers(); if (Modifier.isStatic(inheritedModifiers)) { return false; } if (methodMods == inheritedModifiers) { return true; } if (Modifier.isPrivate(inheritedModifiers)) { return false; } boolean isPackageVisible = !Modifier.isPrivate(inheritedModifiers) && !Modifier.isProtected(inheritedModifiers) && !Modifier.isPublic(inheritedModifiers); if (isPackageVisible && !inSamePackage) { return false; } return true; } /** * This method recurses up a specified type looking for a method that overrides the one passed in. * * @return the method being overridden or null if none is found */ public static void checkForOverride(ResolvedType typeToCheck, String mname, String mparams, String mrettype, int mmods, String mpkg, UnresolvedType[] methodParamsArray, List overriddenMethodsCollector) { if (typeToCheck == null) { return; } if (typeToCheck instanceof MissingResolvedTypeWithKnownSignature) { return; // we just can't tell ! } if (typeToCheck.getWorld().forDEBUG_bridgingCode) { System.err.println(" Bridging:checking for override of " + mname + " in " + typeToCheck); } String packageName = typeToCheck.getPackageName(); if (packageName == null) { packageName = ""; } // used when looking at visibility rules boolean inSamePackage = packageName.equals(mpkg); ResolvedMember[] methods = typeToCheck.getDeclaredMethods(); for (ResolvedMember methodThatMightBeGettingOverridden : methods) { // the method we are going to check ResolvedMember isOverriding = isOverriding(typeToCheck, methodThatMightBeGettingOverridden, mname, mrettype, mmods, inSamePackage, methodParamsArray); if (isOverriding != null) { overriddenMethodsCollector.add(isOverriding); } } // was: List l = typeToCheck.getInterTypeMungers(); List l = (typeToCheck.isRawType() ? typeToCheck.getGenericType().getInterTypeMungers() : typeToCheck .getInterTypeMungers()); for (ConcreteTypeMunger o : l) { // FIXME asc if its not a BcelTypeMunger then its an // EclipseTypeMunger ... do I need to worry about that? if (o instanceof BcelTypeMunger) { BcelTypeMunger element = (BcelTypeMunger) o; if (element.getMunger() instanceof NewMethodTypeMunger) { if (typeToCheck.getWorld().forDEBUG_bridgingCode) { System.err.println("Possible ITD candidate " + element); } ResolvedMember aMethod = element.getSignature(); ResolvedMember isOverriding = isOverriding(typeToCheck, aMethod, mname, mrettype, mmods, inSamePackage, methodParamsArray); if (isOverriding != null) { overriddenMethodsCollector.add(isOverriding); } } } } if (typeToCheck.equals(UnresolvedType.OBJECT)) { return; } ResolvedType superclass = typeToCheck.getSuperclass(); checkForOverride(superclass, mname, mparams, mrettype, mmods, mpkg, methodParamsArray,overriddenMethodsCollector); ResolvedType[] interfaces = typeToCheck.getDeclaredInterfaces(); for (ResolvedType anInterface : interfaces) { checkForOverride(anInterface, mname, mparams, mrettype, mmods, mpkg, methodParamsArray, overriddenMethodsCollector); } } /** * We need to determine if any methods in this type require bridge methods - this method should only be called if necessary to * do this calculation, i.e. we are on a 1.5 VM (where covariance/generics exist) and the type hierarchy for the specified class * has changed (via decp/itd). * * See pr108101 */ public static boolean calculateAnyRequiredBridgeMethods(BcelWorld world, LazyClassGen clazz) { world.ensureAdvancedConfigurationProcessed(); if (!world.isInJava5Mode()) { return false; // just double check... the caller should have already } if (clazz.isInterface()) { return false; // dont bother if we are an interface } // So what methods do we have right now in this class? List methods = clazz.getMethodGens(); // Keep a set of all methods from this type - it'll help us to check if bridge methods // have already been created, we don't want to do it twice! Set methodsSet = new HashSet<>(); for (LazyMethodGen aMethod : methods) { StringBuilder sb = new StringBuilder(aMethod.getName()); sb.append(aMethod.getSignature()); methodsSet.add(sb.toString()); // e.g. "foo(Ljava/lang/String;)V" } List bridges = null; // Now go through all the methods in this type for (LazyMethodGen bridgeToCandidate : methods) { // This is the local method that we *might* have to bridge to if (bridgeToCandidate.isBridgeMethod()) { continue; // Doh! } String name = bridgeToCandidate.getName(); String psig = bridgeToCandidate.getParameterSignature(); String rsig = bridgeToCandidate.getReturnType().getSignature(); // if (bridgeToCandidate.isAbstract()) continue; if (bridgeToCandidate.isStatic()) { continue; // ignore static methods } if (name.endsWith("init>")) { continue; // Skip constructors and static initializers } if (world.forDEBUG_bridgingCode) { System.err.println("Bridging: Determining if we have to bridge to " + clazz.getName() + "." + name + "" + bridgeToCandidate.getSignature()); } // Let's take a look at the superclass ResolvedType theSuperclass = clazz.getSuperClass(); if (world.forDEBUG_bridgingCode) { System.err.println("Bridging: Checking supertype " + theSuperclass); } String pkgName = clazz.getPackageName(); UnresolvedType[] bm = BcelWorld.fromBcel(bridgeToCandidate.getArgumentTypes()); List overriddenMethodsCollector = new ArrayList<>(); checkForOverride(theSuperclass, name, psig, rsig, bridgeToCandidate.getAccessFlags(), pkgName, bm, overriddenMethodsCollector); if (overriddenMethodsCollector.size() != 0) { for (ResolvedMember overriddenMethod : overriddenMethodsCollector) { String key = new StringBuilder(overriddenMethod.getName()).append(overriddenMethod.getSignatureErased()).toString(); // pr237419 boolean alreadyHaveABridgeMethod = methodsSet.contains(key); if (!alreadyHaveABridgeMethod) { if (world.forDEBUG_bridgingCode) { System.err.println("Bridging:bridging to '" + overriddenMethod + "'"); } if (bridges== null) { bridges = new ArrayList<>(); } bridges.add(new BridgeMethodDescriptor(bridgeToCandidate, overriddenMethod)); //createBridgeMethod(world, bridgeToCandidate, clazz, overriddenMethod); methodsSet.add(key); } } } // Check superinterfaces String[] interfaces = clazz.getInterfaceNames(); for (String anInterface : interfaces) { if (world.forDEBUG_bridgingCode) { System.err.println("Bridging:checking superinterface " + anInterface); } ResolvedType interfaceType = world.resolve(anInterface); overriddenMethodsCollector.clear(); checkForOverride(interfaceType, name, psig, rsig, bridgeToCandidate.getAccessFlags(), clazz.getPackageName(), bm, overriddenMethodsCollector); for (ResolvedMember overriddenMethod : overriddenMethodsCollector) { String key = new StringBuilder().append(overriddenMethod.getName()).append(overriddenMethod.getSignatureErased()).toString(); // pr237419 boolean alreadyHaveABridgeMethod = methodsSet.contains(key); if (!alreadyHaveABridgeMethod) { if (bridges== null) { bridges = new ArrayList<>(); } bridges.add(new BridgeMethodDescriptor(bridgeToCandidate, overriddenMethod)); // createBridgeMethod(world, bridgeToCandidate, clazz, overriddenMethod); methodsSet.add(key); if (world.forDEBUG_bridgingCode) { System.err.println("Bridging:bridging to " + overriddenMethod); } } } } } if (bridges != null) { for (BridgeMethodDescriptor bmDescriptor: bridges) { createBridgeMethod(world, bmDescriptor.bridgeToCandidate, clazz, bmDescriptor.overriddenMethod); } } return bridges!=null && !bridges.isEmpty(); } static class BridgeMethodDescriptor { final LazyMethodGen bridgeToCandidate; final ResolvedMember overriddenMethod; public BridgeMethodDescriptor(LazyMethodGen bridgeToCandidate, ResolvedMember overriddenMethod) { this.bridgeToCandidate = bridgeToCandidate; this.overriddenMethod = overriddenMethod; } } // **************************** end of bridge method creation code ***************** /** * Weave any declare @method/@ctor statements into the members of the supplied class */ private boolean weaveDeclareAtMethodCtor(LazyClassGen clazz) { List reportedProblems = new ArrayList<>(); List allDecams = world.getDeclareAnnotationOnMethods(); if (allDecams.isEmpty()) { return false; } boolean isChanged = false; // deal with ITDs List itdMethodsCtors = getITDSubset(clazz, ResolvedTypeMunger.Method); itdMethodsCtors.addAll(getITDSubset(clazz, ResolvedTypeMunger.Constructor)); if (!itdMethodsCtors.isEmpty()) { // Can't use the subset called 'decaMs' as it won't be right for // ITDs... isChanged = weaveAtMethodOnITDSRepeatedly(allDecams, itdMethodsCtors, reportedProblems); } List decaMs = getMatchingSubset(allDecams, clazz.getType()); if (decaMs.isEmpty()) { return false; // nothing to do } Set unusedDecams = new HashSet<>(decaMs); // These methods may have been targeted with declare annotation. Example: ITD on an interface // where the top most implementor gets a real method. The top most implementor method // is an 'addedLazyMethodGen' if (addedLazyMethodGens!=null) { for (LazyMethodGen method: addedLazyMethodGens) { // They have no resolvedmember of their own, conjure one up for matching purposes ResolvedMember resolvedmember = new ResolvedMemberImpl(ResolvedMember.METHOD,method.getEnclosingClass().getType(),method.getAccessFlags(), BcelWorld.fromBcel(method.getReturnType()),method.getName(), BcelWorld.fromBcel(method.getArgumentTypes()),UnresolvedType.forNames(method.getDeclaredExceptions())); resolvedmember.setAnnotationTypes(method.getAnnotationTypes()); resolvedmember.setAnnotations(method.getAnnotations()); List worthRetrying = new ArrayList<>(); boolean modificationOccured = false; for (DeclareAnnotation decam: decaMs) { if (decam.matches(resolvedmember, world)) { if (doesAlreadyHaveAnnotation(resolvedmember, decam, reportedProblems,false)) { // remove the declare @method since don't want an error when the annotation is already there unusedDecams.remove(decam); continue; } AnnotationGen a = ((BcelAnnotation) decam.getAnnotation()).getBcelAnnotation(); // create copy to get the annotation type into the right constant pool AnnotationAJ aj = new BcelAnnotation(new AnnotationGen(a, clazz.getConstantPool(), true),world); method.addAnnotation(aj); resolvedmember.addAnnotation(decam.getAnnotation()); AsmRelationshipProvider.addDeclareAnnotationMethodRelationship(decam.getSourceLocation(), clazz.getName(), resolvedmember, world.getModelAsAsmManager()); reportMethodCtorWeavingMessage(clazz, resolvedmember, decam, method.getDeclarationLineNumber()); isChanged = true; modificationOccured = true; unusedDecams.remove(decam); } else if (!decam.isStarredAnnotationPattern()) { // an annotation is specified that might be put on by a subsequent decaf worthRetrying.add(decam); } } // Multiple secondary passes while (!worthRetrying.isEmpty() && modificationOccured) { modificationOccured = false; // lets have another go List forRemoval = new ArrayList<>(); for (DeclareAnnotation decam : worthRetrying) { if (decam.matches(resolvedmember, world)) { if (doesAlreadyHaveAnnotation(resolvedmember, decam, reportedProblems,false)) { // remove the declare @method since don't // want an error when // the annotation is already there unusedDecams.remove(decam); continue; // skip this one... } AnnotationGen a = ((BcelAnnotation) decam.getAnnotation()).getBcelAnnotation(); // create copy to get the annotation type into the right constant pool AnnotationAJ aj = new BcelAnnotation(new AnnotationGen(a, clazz.getConstantPool(), true),world); method.addAnnotation(aj); resolvedmember.addAnnotation(decam.getAnnotation()); AsmRelationshipProvider.addDeclareAnnotationMethodRelationship(decam.getSourceLocation(), clazz.getName(), resolvedmember, world.getModelAsAsmManager());// getMethod()); isChanged = true; modificationOccured = true; forRemoval.add(decam); unusedDecams.remove(decam); } } worthRetrying.removeAll(forRemoval); } } } // deal with all the other methods... List members = clazz.getMethodGens(); if (!members.isEmpty()) { for (int memberCounter = 0; memberCounter < members.size(); memberCounter++) { LazyMethodGen mg = members.get(memberCounter); if (!mg.getName().startsWith(NameMangler.PREFIX)) { // Single first pass List worthRetrying = new ArrayList<>(); boolean modificationOccured = false; List annotationsToAdd = null; for (DeclareAnnotation decaM : decaMs) { if (decaM.matches(mg.getMemberView(), world)) { if (doesAlreadyHaveAnnotation(mg.getMemberView(), decaM, reportedProblems,true)) { // remove the declare @method since don't want // an error when the annotation is already there unusedDecams.remove(decaM); continue; // skip this one... } if (annotationsToAdd == null) { annotationsToAdd = new ArrayList<>(); } BcelAnnotation decaMAnnotation = (BcelAnnotation) decaM.getAnnotation(); if (decaMAnnotation == null) { unusedDecams.remove(decaM); continue; } AnnotationGen a = decaMAnnotation.getBcelAnnotation(); AnnotationGen ag = new AnnotationGen(a, clazz.getConstantPool(), true); annotationsToAdd.add(ag); mg.addAnnotation(decaM.getAnnotation()); AsmRelationshipProvider.addDeclareAnnotationMethodRelationship(decaM.getSourceLocation(), clazz.getName(), mg.getMemberView(), world.getModelAsAsmManager());// getMethod()); reportMethodCtorWeavingMessage(clazz, mg.getMemberView(), decaM, mg.getDeclarationLineNumber()); isChanged = true; modificationOccured = true; // remove the declare @method since have matched // against it unusedDecams.remove(decaM); } else { if (!decaM.isStarredAnnotationPattern()) { worthRetrying.add(decaM); // an annotation is // specified that // might be put on // by a subsequent // decaf } } } // Multiple secondary passes while (!worthRetrying.isEmpty() && modificationOccured) { modificationOccured = false; // lets have another go List forRemoval = new ArrayList<>(); for (DeclareAnnotation decaM : worthRetrying) { if (decaM.matches(mg.getMemberView(), world)) { if (doesAlreadyHaveAnnotation(mg.getMemberView(), decaM, reportedProblems,true)) { // remove the declare @method since don't // want an error when // the annotation is already there unusedDecams.remove(decaM); continue; // skip this one... } if (annotationsToAdd == null) { annotationsToAdd = new ArrayList<>(); } AnnotationGen a = ((BcelAnnotation) decaM.getAnnotation()).getBcelAnnotation(); // create copy to get the annotation type into the right constant pool AnnotationGen ag = new AnnotationGen(a, clazz.getConstantPool(), true); annotationsToAdd.add(ag); mg.addAnnotation(decaM.getAnnotation()); AsmRelationshipProvider.addDeclareAnnotationMethodRelationship(decaM.getSourceLocation(), clazz.getName(), mg.getMemberView(), world.getModelAsAsmManager());// getMethod()); isChanged = true; modificationOccured = true; forRemoval.add(decaM); // remove the declare @method since have matched // against it unusedDecams.remove(decaM); } } worthRetrying.removeAll(forRemoval); } if (annotationsToAdd != null) { Method oldMethod = mg.getMethod(); MethodGen myGen = new MethodGen(oldMethod, clazz.getClassName(), clazz.getConstantPool(), false); for (AnnotationGen a : annotationsToAdd) { myGen.addAnnotation(a); } Method newMethod = myGen.getMethod(); members.set(memberCounter, new LazyMethodGen(newMethod, clazz)); } } } checkUnusedDeclareAts(unusedDecams, false); } return isChanged; } // TAG: WeavingMessage private void reportMethodCtorWeavingMessage(LazyClassGen clazz, ResolvedMember member, DeclareAnnotation decaM, int memberLineNumber) { if (!getWorld().getMessageHandler().isIgnoring(IMessage.WEAVEINFO)) { StringBuilder parmString = new StringBuilder("("); UnresolvedType[] paramTypes = member.getParameterTypes(); for (int i = 0; i < paramTypes.length; i++) { UnresolvedType type = paramTypes[i]; String s = org.aspectj.apache.bcel.classfile.Utility.signatureToString(type.getSignature()); if (s.lastIndexOf('.') != -1) { s = s.substring(s.lastIndexOf('.') + 1); } parmString.append(s); if ((i + 1) < paramTypes.length) { parmString.append(","); } } parmString.append(")"); String methodName = member.getName(); StringBuilder sig = new StringBuilder(); sig.append(org.aspectj.apache.bcel.classfile.Utility.accessToString(member.getModifiers())); sig.append(" "); sig.append(member.getReturnType().toString()); sig.append(" "); sig.append(member.getDeclaringType().toString()); sig.append("."); sig.append(methodName.equals("") ? "new" : methodName); sig.append(parmString); StringBuilder loc = new StringBuilder(); if (clazz.getFileName() == null) { loc.append("no debug info available"); } else { loc.append(clazz.getFileName()); if (memberLineNumber != -1) { loc.append(":" + memberLineNumber); } } getWorld().getMessageHandler().handleMessage( WeaveMessage.constructWeavingMessage( WeaveMessage.WEAVEMESSAGE_ANNOTATES, new String[] { sig.toString(), loc.toString(), decaM.getAnnotationString(), methodName.startsWith("") ? "constructor" : "method", decaM.getAspect().toString(), Utility.beautifyLocation(decaM.getSourceLocation()) })); } } /** * Looks through a list of declare annotation statements and only returns those that could possibly match on a field/method/ctor * in type. */ private List getMatchingSubset(List declareAnnotations, ResolvedType type) { List subset = new ArrayList<>(); for (DeclareAnnotation da : declareAnnotations) { if (da.couldEverMatch(type)) { subset.add(da); } } return subset; } /** * Get a subset of all the type mungers defined on this aspect */ private List getITDSubset(LazyClassGen clazz, ResolvedTypeMunger.Kind wantedKind) { List subset = new ArrayList<>(); for (ConcreteTypeMunger typeMunger : clazz.getBcelObjectType().getTypeMungers()) { if (typeMunger.getMunger().getKind() == wantedKind) { subset.add(typeMunger); } } return subset; } public LazyMethodGen locateAnnotationHolderForFieldMunger(LazyClassGen clazz, ConcreteTypeMunger fieldMunger) { NewFieldTypeMunger newFieldMunger = (NewFieldTypeMunger) fieldMunger.getMunger(); ResolvedMember lookingFor = AjcMemberMaker.interFieldInitializer(newFieldMunger.getSignature(), clazz.getType()); for (LazyMethodGen method : clazz.getMethodGens()) { if (method.getName().equals(lookingFor.getName())) { return method; } } return null; } // FIXME asc refactor this to neaten it up public LazyMethodGen locateAnnotationHolderForMethodCtorMunger(LazyClassGen clazz, ConcreteTypeMunger methodCtorMunger) { ResolvedTypeMunger rtMunger = methodCtorMunger.getMunger(); ResolvedMember lookingFor = null; if (rtMunger instanceof NewMethodTypeMunger) { NewMethodTypeMunger nftm = (NewMethodTypeMunger) rtMunger; lookingFor = AjcMemberMaker.interMethodDispatcher(nftm.getSignature(), methodCtorMunger.getAspectType()); } else if (rtMunger instanceof NewConstructorTypeMunger) { NewConstructorTypeMunger nftm = (NewConstructorTypeMunger) rtMunger; lookingFor = AjcMemberMaker.postIntroducedConstructor(methodCtorMunger.getAspectType(), nftm.getSignature() .getDeclaringType(), nftm.getSignature().getParameterTypes()); } else { throw new BCException("Not sure what this is: " + methodCtorMunger); } String name = lookingFor.getName(); String paramSignature = lookingFor.getParameterSignature(); for (LazyMethodGen member : clazz.getMethodGens()) { if (member.getName().equals(name) && member.getParameterSignature().equals(paramSignature)) { return member; } } return null; } /** * Applies some set of declare @field constructs (List) to some bunch of ITDfields (List. It * will iterate over the fields repeatedly until everything has been applied. * */ private boolean weaveAtFieldRepeatedly(List decaFs, List itdFields, List reportedErrors) { boolean isChanged = false; for (ConcreteTypeMunger itdField : itdFields) { BcelTypeMunger fieldMunger = (BcelTypeMunger) itdField; ResolvedMember itdIsActually = fieldMunger.getSignature(); Set worthRetrying = new LinkedHashSet<>(); boolean modificationOccured = false; for (DeclareAnnotation decaF : decaFs) { if (decaF.matches(itdIsActually, world)) { if (decaF.isRemover()) { LazyMethodGen annotationHolder = locateAnnotationHolderForFieldMunger(clazz, fieldMunger); if (annotationHolder.hasAnnotation(decaF.getAnnotationType())) { isChanged = true; // something to remove annotationHolder.removeAnnotation(decaF.getAnnotationType()); AsmRelationshipProvider.addDeclareAnnotationRelationship(world.getModelAsAsmManager(), decaF.getSourceLocation(), itdIsActually.getSourceLocation(), true); } else { worthRetrying.add(decaF); } } else { LazyMethodGen annotationHolder = locateAnnotationHolderForFieldMunger(clazz, fieldMunger); if (doesAlreadyHaveAnnotation(annotationHolder, itdIsActually, decaF, reportedErrors)) { continue; // skip this one... } annotationHolder.addAnnotation(decaF.getAnnotation()); AsmRelationshipProvider.addDeclareAnnotationRelationship(world.getModelAsAsmManager(), decaF.getSourceLocation(), itdIsActually.getSourceLocation(), false); isChanged = true; modificationOccured = true; } } else { if (!decaF.isStarredAnnotationPattern()) { worthRetrying.add(decaF); // an annotation is specified // that might be put on by a // subsequent decaf } } } while (!worthRetrying.isEmpty() && modificationOccured) { modificationOccured = false; List forRemoval = new ArrayList<>(); for (DeclareAnnotation decaF : worthRetrying) { if (decaF.matches(itdIsActually, world)) { if (decaF.isRemover()) { LazyMethodGen annotationHolder = locateAnnotationHolderForFieldMunger(clazz, fieldMunger); if (annotationHolder.hasAnnotation(decaF.getAnnotationType())) { isChanged = true; // something to remove annotationHolder.removeAnnotation(decaF.getAnnotationType()); AsmRelationshipProvider.addDeclareAnnotationRelationship(world.getModelAsAsmManager(), decaF.getSourceLocation(), itdIsActually.getSourceLocation(), true); forRemoval.add(decaF); } } else { LazyMethodGen annotationHolder = locateAnnotationHolderForFieldMunger(clazz, fieldMunger); if (doesAlreadyHaveAnnotation(annotationHolder, itdIsActually, decaF, reportedErrors)) { continue; // skip this one... } annotationHolder.addAnnotation(decaF.getAnnotation()); AsmRelationshipProvider.addDeclareAnnotationRelationship(world.getModelAsAsmManager(), decaF.getSourceLocation(), itdIsActually.getSourceLocation(), false); isChanged = true; modificationOccured = true; forRemoval.add(decaF); } } } worthRetrying.removeAll(forRemoval); } } return isChanged; } /** * Applies some set of declare @method/@ctor constructs (List) to some bunch of ITDmembers * (List. It will iterate over the fields repeatedly until everything has been applied. */ private boolean weaveAtMethodOnITDSRepeatedly(List decaMCs, List itdsForMethodAndConstructor, List reportedErrors) { boolean isChanged = false; AsmManager asmManager = world.getModelAsAsmManager(); for (ConcreteTypeMunger methodctorMunger : itdsForMethodAndConstructor) { // for (Iterator iter = itdsForMethodAndConstructor.iterator(); iter.hasNext();) { // BcelTypeMunger methodctorMunger = (BcelTypeMunger) iter.next(); ResolvedMember unMangledInterMethod = methodctorMunger.getSignature(); List worthRetrying = new ArrayList<>(); boolean modificationOccured = false; for (DeclareAnnotation decaMC : decaMCs) { if (decaMC.matches(unMangledInterMethod, world)) { LazyMethodGen annotationHolder = locateAnnotationHolderForMethodCtorMunger(clazz, methodctorMunger); if (annotationHolder == null || doesAlreadyHaveAnnotation(annotationHolder, unMangledInterMethod, decaMC, reportedErrors)) { continue; // skip this one... } annotationHolder.addAnnotation(decaMC.getAnnotation()); isChanged = true; AsmRelationshipProvider.addDeclareAnnotationRelationship(asmManager, decaMC.getSourceLocation(), unMangledInterMethod.getSourceLocation(), false); reportMethodCtorWeavingMessage(clazz, unMangledInterMethod, decaMC, -1); modificationOccured = true; } else { // If an annotation is specified, it might be added by one of the other declare annotation statements if (!decaMC.isStarredAnnotationPattern()) { worthRetrying.add(decaMC); } } } while (!worthRetrying.isEmpty() && modificationOccured) { modificationOccured = false; List forRemoval = new ArrayList<>(); for (DeclareAnnotation decaMC : worthRetrying) { if (decaMC.matches(unMangledInterMethod, world)) { LazyMethodGen annotationHolder = locateAnnotationHolderForFieldMunger(clazz, methodctorMunger); if (doesAlreadyHaveAnnotation(annotationHolder, unMangledInterMethod, decaMC, reportedErrors)) { continue; // skip this one... } annotationHolder.addAnnotation(decaMC.getAnnotation()); unMangledInterMethod.addAnnotation(decaMC.getAnnotation()); AsmRelationshipProvider.addDeclareAnnotationRelationship(asmManager, decaMC.getSourceLocation(), unMangledInterMethod.getSourceLocation(), false); isChanged = true; modificationOccured = true; forRemoval.add(decaMC); } worthRetrying.removeAll(forRemoval); } } } return isChanged; } private boolean dontAddTwice(DeclareAnnotation decaF, AnnotationAJ[] dontAddMeTwice) { for (AnnotationAJ ann : dontAddMeTwice) { if (ann != null && decaF.getAnnotation().getTypeName().equals(ann.getTypeName())) { return true; } } return false; } /** * Remove an annotation from the supplied array, if it is in there. */ private AnnotationAJ[] removeFromAnnotationsArray(AnnotationAJ[] annotations,AnnotationAJ annotation) { for (int i=0;i reportedProblems = new ArrayList<>(); List allDecafs = world.getDeclareAnnotationOnFields(); if (allDecafs.isEmpty()) { return false; } boolean typeIsChanged = false; List relevantItdFields = getITDSubset(clazz, ResolvedTypeMunger.Field); if (relevantItdFields != null) { typeIsChanged = weaveAtFieldRepeatedly(allDecafs, relevantItdFields, reportedProblems); } List decafs = getMatchingSubset(allDecafs, clazz.getType()); if (decafs.isEmpty()) { return typeIsChanged; } List fields = clazz.getFieldGens(); if (fields != null) { Set unusedDecafs = new HashSet<>(decafs); for (BcelField field : fields) { if (!field.getName().startsWith(NameMangler.PREFIX)) { // Single first pass Set worthRetrying = new LinkedHashSet<>(); boolean modificationOccured = false; AnnotationAJ[] dontAddMeTwice = field.getAnnotations(); // go through all the declare @field statements for (DeclareAnnotation decaf : decafs) { if (decaf.getAnnotation() == null) { unusedDecafs.remove(decaf); continue; } if (decaf.matches(field, world)) { if (decaf.isRemover()) { AnnotationAJ annotation = decaf.getAnnotation(); if (field.hasAnnotation(annotation.getType())) { // something to remove typeIsChanged = true; field.removeAnnotation(annotation); AsmRelationshipProvider.addDeclareAnnotationFieldRelationship(world.getModelAsAsmManager(), decaf.getSourceLocation(), clazz.getName(), field, true); reportFieldAnnotationWeavingMessage(clazz, field, decaf, true); dontAddMeTwice = removeFromAnnotationsArray(dontAddMeTwice, annotation); } else { worthRetrying.add(decaf); } unusedDecafs.remove(decaf); } else { if (!dontAddTwice(decaf, dontAddMeTwice)) { if (doesAlreadyHaveAnnotation(field, decaf, reportedProblems,true )) { // remove the declare @field since don't want an error when the annotation is already there unusedDecafs.remove(decaf); continue; } field.addAnnotation(decaf.getAnnotation()); } AsmRelationshipProvider.addDeclareAnnotationFieldRelationship(world.getModelAsAsmManager(), decaf.getSourceLocation(), clazz.getName(), field, false); reportFieldAnnotationWeavingMessage(clazz, field, decaf, false); typeIsChanged = true; modificationOccured = true; unusedDecafs.remove(decaf); } } else if (!decaf.isStarredAnnotationPattern() || decaf.isRemover()) { worthRetrying.add(decaf); // an annotation is specified that might be put on by a subsequent decaf } } // Multiple secondary passes while (!worthRetrying.isEmpty() && modificationOccured) { modificationOccured = false; // lets have another go with any remaining ones List forRemoval = new ArrayList<>(); for (DeclareAnnotation decaF : worthRetrying) { if (decaF.matches(field, world)) { if (decaF.isRemover()) { AnnotationAJ annotation = decaF.getAnnotation(); if (field.hasAnnotation(annotation.getType())) { // something to remove typeIsChanged = modificationOccured = true; forRemoval.add(decaF); field.removeAnnotation(annotation); AsmRelationshipProvider.addDeclareAnnotationFieldRelationship(world.getModelAsAsmManager(), decaF.getSourceLocation(), clazz.getName(), field, true); reportFieldAnnotationWeavingMessage(clazz, field, decaF, true); } } else { // below code is for recursive things unusedDecafs.remove(decaF); if (doesAlreadyHaveAnnotation(field, decaF, reportedProblems, true)) { continue; } field.addAnnotation(decaF.getAnnotation()); AsmRelationshipProvider.addDeclareAnnotationFieldRelationship(world.getModelAsAsmManager(), decaF.getSourceLocation(), clazz.getName(), field, false); typeIsChanged = modificationOccured = true; forRemoval.add(decaF); } } } worthRetrying.removeAll(forRemoval); } } } checkUnusedDeclareAts(unusedDecafs, true); } return typeIsChanged; } // bug 99191 - put out an error message if the type doesn't exist /** * Report an error if the reason a "declare @method/ctor/field" was not used was because the member specified does not exist. * This method is passed some set of declare statements that didn't match and a flag indicating whether the set contains declare @field * or declare @method/ctor entries. */ private void checkUnusedDeclareAts(Set unusedDecaTs, boolean isDeclareAtField) { for (DeclareAnnotation declA : unusedDecaTs) { // Error if an exact type pattern was specified boolean shouldCheck = declA.isExactPattern() || declA.getSignaturePattern().getExactDeclaringTypes().size() != 0; if (shouldCheck && declA.getKind() != DeclareAnnotation.AT_CONSTRUCTOR) { if (declA.getSignaturePattern().isMatchOnAnyName()) { shouldCheck = false; } else { List declaringTypePatterns = declA.getSignaturePattern().getExactDeclaringTypes(); if (declaringTypePatterns.size() == 0) { shouldCheck = false; } else { for (ExactTypePattern exactTypePattern : declaringTypePatterns) { if (exactTypePattern.isIncludeSubtypes()) { shouldCheck = false; break; } } } } } if (shouldCheck) { // Quickly check if an ITD supplies the 'missing' member boolean itdMatch = false; List lst = clazz.getType().getInterTypeMungers(); for (Iterator iterator = lst.iterator(); iterator.hasNext() && !itdMatch;) { ConcreteTypeMunger element = iterator.next(); if (element.getMunger() instanceof NewFieldTypeMunger) { NewFieldTypeMunger nftm = (NewFieldTypeMunger) element.getMunger(); itdMatch = declA.matches(nftm.getSignature(), world); } else if (element.getMunger() instanceof NewMethodTypeMunger) { NewMethodTypeMunger nmtm = (NewMethodTypeMunger) element.getMunger(); itdMatch = declA.matches(nmtm.getSignature(), world); } else if (element.getMunger() instanceof NewConstructorTypeMunger) { NewConstructorTypeMunger nctm = (NewConstructorTypeMunger) element.getMunger(); itdMatch = declA.matches(nctm.getSignature(), world); } } if (!itdMatch) { IMessage message = null; if (isDeclareAtField) { message = new Message("The field '" + declA.getSignaturePattern().toString() + "' does not exist", declA.getSourceLocation(), true); } else { message = new Message("The method '" + declA.getSignaturePattern().toString() + "' does not exist", declA.getSourceLocation(), true); } world.getMessageHandler().handleMessage(message); } } } } // TAG: WeavingMessage private void reportFieldAnnotationWeavingMessage(LazyClassGen clazz, BcelField theField, DeclareAnnotation decaf, boolean isRemove) { if (!getWorld().getMessageHandler().isIgnoring(IMessage.WEAVEINFO)) { world.getMessageHandler().handleMessage( WeaveMessage.constructWeavingMessage( isRemove ? WeaveMessage.WEAVEMESSAGE_REMOVES_ANNOTATION : WeaveMessage.WEAVEMESSAGE_ANNOTATES, new String[] { theField.getFieldAsIs().toString() + "' of type '" + clazz.getName(), clazz.getFileName(), decaf.getAnnotationString(), "field", decaf.getAspect().toString(), Utility.beautifyLocation(decaf.getSourceLocation()) })); } } /** * Check if a resolved member (field/method/ctor) already has an annotation, if it does then put out a warning and return true */ private boolean doesAlreadyHaveAnnotation(ResolvedMember rm, DeclareAnnotation deca, List reportedProblems, boolean reportError) { if (rm.hasAnnotation(deca.getAnnotationType())) { if (reportError && world.getLint().elementAlreadyAnnotated.isEnabled()) { Integer uniqueID = rm.hashCode() * deca.hashCode(); if (!reportedProblems.contains(uniqueID)) { reportedProblems.add(uniqueID); world.getLint().elementAlreadyAnnotated.signal(new String[] { rm.toString(), deca.getAnnotationType().toString() }, rm.getSourceLocation(), new ISourceLocation[] { deca.getSourceLocation() }); } } return true; } return false; } private boolean doesAlreadyHaveAnnotation(LazyMethodGen rm, ResolvedMember itdfieldsig, DeclareAnnotation deca, List reportedProblems) { if (rm != null && rm.hasAnnotation(deca.getAnnotationType())) { if (world.getLint().elementAlreadyAnnotated.isEnabled()) { Integer uniqueID = rm.hashCode() * deca.hashCode(); if (!reportedProblems.contains(uniqueID)) { reportedProblems.add(uniqueID); reportedProblems.add(itdfieldsig.hashCode() * deca.hashCode()); world.getLint().elementAlreadyAnnotated.signal(new String[] { itdfieldsig.toString(), deca.getAnnotationType().toString() }, rm.getSourceLocation(), new ISourceLocation[] { deca.getSourceLocation() }); } } return true; } return false; } private Set findAspectsForMungers(LazyMethodGen mg) { Set aspectsAffectingType = new HashSet<>(); for (BcelShadow shadow : mg.matchedShadows) { for (ShadowMunger munger : shadow.getMungers()) { if (munger instanceof BcelAdvice) { BcelAdvice bcelAdvice = (BcelAdvice) munger; if (bcelAdvice.getConcreteAspect() != null) { aspectsAffectingType.add(bcelAdvice.getConcreteAspect().getSignature()); } } else { // It is a 'Checker' - we don't need to remember aspects // that only contributed Checkers... } } } return aspectsAffectingType; } private boolean inlineSelfConstructors(List methodGens, List recursiveCtors) { boolean inlinedSomething = false; List newRecursiveCtors = new ArrayList<>(); for (LazyMethodGen methodGen : methodGens) { if (!methodGen.getName().equals("")) { continue; } InstructionHandle ih = findSuperOrThisCall(methodGen); if (ih != null && isThisCall(ih)) { LazyMethodGen donor = getCalledMethod(ih); if (donor.equals(methodGen)) { newRecursiveCtors.add(donor); } else { if (!recursiveCtors.contains(donor)) { inlineMethod(donor, methodGen, ih); inlinedSomething = true; } } } } recursiveCtors.addAll(newRecursiveCtors); return inlinedSomething; } private void positionAndImplement(List initializationShadows) { for (BcelShadow s : initializationShadows) { positionInitializationShadow(s); // s.getEnclosingMethod().print(); s.implement(); } } private void positionInitializationShadow(BcelShadow s) { LazyMethodGen mg = s.getEnclosingMethod(); InstructionHandle call = findSuperOrThisCall(mg); InstructionList body = mg.getBody(); ShadowRange r = new ShadowRange(body); r.associateWithShadow(s); if (s.getKind() == Shadow.PreInitialization) { // XXX assert first instruction is an ALOAD_0. // a pre shadow goes from AFTER the first instruction (which we // believe to // be an ALOAD_0) to just before the call to super r.associateWithTargets(Range.genStart(body, body.getStart().getNext()), Range.genEnd(body, call.getPrev())); } else { // assert s.getKind() == Shadow.Initialization r.associateWithTargets(Range.genStart(body, call.getNext()), Range.genEnd(body)); } } private boolean isThisCall(InstructionHandle ih) { InvokeInstruction inst = (InvokeInstruction) ih.getInstruction(); return inst.getClassName(cpg).equals(clazz.getName()); } /** * inline a particular call in bytecode. * * @param donor the method we want to inline * @param recipient the method containing the call we want to inline * @param call the instructionHandle in recipient's body holding the call we want to inline. */ public static void inlineMethod(LazyMethodGen donor, LazyMethodGen recipient, InstructionHandle call) { // assert recipient.contains(call) /* * Implementation notes: * * We allocate two slots for every tempvar so we don't screw up longs and doubles which may share space. This could be * conservatively avoided (no reference to a long/double instruction, don't do it) or packed later. Right now we don't * bother to pack. * * Allocate a new var for each formal param of the inlined. Fill with stack contents. Then copy the inlined instructions in * with the appropriate remap table. Any framelocs used by locals in inlined are reallocated to top of frame, */ final InstructionFactory fact = recipient.getEnclosingClass().getFactory(); IntMap frameEnv = new IntMap(); // this also sets up the initial environment InstructionList argumentStores = genArgumentStores(donor, recipient, frameEnv, fact); InstructionList inlineInstructions = genInlineInstructions(donor, recipient, frameEnv, fact, false); inlineInstructions.insert(argumentStores); recipient.getBody().append(call, inlineInstructions); Utility.deleteInstruction(call, recipient); } // public BcelVar genTempVar(UnresolvedType typeX) { // return new BcelVar(typeX.resolve(world), // genTempVarIndex(typeX.getSize())); // } // // private int genTempVarIndex(int size) { // return enclosingMethod.allocateLocal(size); // } /** * Input method is a synchronized method, we remove the bit flag for synchronized and then insert a try..finally block * * Some jumping through firey hoops required - depending on the input code level (1.5 or not) we may or may not be able to use * the LDC instruction that takes a class literal (doesnt on <1.5). * * FIXME asc Before promoting -Xjoinpoints:synchronization to be a standard option, this needs a bunch of tidying up - there is * some duplication that can be removed. */ public static void transformSynchronizedMethod(LazyMethodGen synchronizedMethod) { if (trace.isTraceEnabled()) { trace.enter("transformSynchronizedMethod", synchronizedMethod); } // System.err.println("DEBUG: Transforming synchronized method: "+ // synchronizedMethod.getName()); final InstructionFactory fact = synchronizedMethod.getEnclosingClass().getFactory(); InstructionList body = synchronizedMethod.getBody(); InstructionList prepend = new InstructionList(); Type enclosingClassType = BcelWorld.makeBcelType(synchronizedMethod.getEnclosingClass().getType()); // STATIC METHOD TRANSFORMATION if (synchronizedMethod.isStatic()) { // What to do here depends on the level of the class file! // LDC can handle class literals in Java5 and above *sigh* if (synchronizedMethod.getEnclosingClass().isAtLeastJava5()) { // MONITORENTER logic: // 0: ldc #2; //class C // 2: dup // 3: astore_0 // 4: monitorenter int slotForLockObject = synchronizedMethod.allocateLocal(enclosingClassType); prepend.append(fact.createConstant(enclosingClassType)); prepend.append(InstructionFactory.createDup(1)); prepend.append(InstructionFactory.createStore(enclosingClassType, slotForLockObject)); prepend.append(InstructionFactory.MONITORENTER); // MONITOREXIT logic: // We basically need to wrap the code from the method in a // finally block that // will ensure monitorexit is called. Content on the finally // block seems to // be always: // // E1: ALOAD_1 // MONITOREXIT // ATHROW // // so lets build that: InstructionList finallyBlock = new InstructionList(); finallyBlock.append(InstructionFactory.createLoad(Type.getType(java.lang.Class.class), slotForLockObject)); finallyBlock.append(InstructionConstants.MONITOREXIT); finallyBlock.append(InstructionConstants.ATHROW); // finally -> E1 // | GETSTATIC java.lang.System.out Ljava/io/PrintStream; (line // 21) // | LDC "hello" // | INVOKEVIRTUAL java.io.PrintStream.println // (Ljava/lang/String;)V // | ALOAD_1 (line 20) // | MONITOREXIT // finally -> E1 // GOTO L0 // finally -> E1 // | E1: ALOAD_1 // | MONITOREXIT // finally -> E1 // ATHROW // L0: RETURN (line 23) // search for 'returns' and make them jump to the // aload_,monitorexit InstructionHandle walker = body.getStart(); List rets = new ArrayList<>(); while (walker != null) { if (walker.getInstruction().isReturnInstruction()) { rets.add(walker); } walker = walker.getNext(); } if (!rets.isEmpty()) { // need to ensure targeters for 'return' now instead target // the load instruction // (so we never jump over the monitorexit logic) for (InstructionHandle element : rets) { InstructionList monitorExitBlock = new InstructionList(); monitorExitBlock.append(InstructionFactory.createLoad(enclosingClassType, slotForLockObject)); monitorExitBlock.append(InstructionConstants.MONITOREXIT); // monitorExitBlock.append(Utility.copyInstruction(element // .getInstruction())); // element.setInstruction(InstructionFactory.createLoad( // classType,slotForThis)); InstructionHandle monitorExitBlockStart = body.insert(element, monitorExitBlock); // now move the targeters from the RET to the start of // the monitorexit block for (InstructionTargeter targeter : element.getTargetersCopy()) { // what kinds are there? if (targeter instanceof LocalVariableTag) { // ignore } else if (targeter instanceof LineNumberTag) { // ignore // } else if (targeter instanceof // InstructionBranch && // ((InstructionBranch)targeter).isGoto()) { // // move it... // targeter.updateTarget(element, // monitorExitBlockStart); } else if (targeter instanceof InstructionBranch) { // move it targeter.updateTarget(element, monitorExitBlockStart); } else { throw new BCException("Unexpected targeter encountered during transform: " + targeter); } } } } // now the magic, putting the finally block around the code InstructionHandle finallyStart = finallyBlock.getStart(); InstructionHandle tryPosition = body.getStart(); InstructionHandle catchPosition = body.getEnd(); body.insert(body.getStart(), prepend); // now we can put the // monitorenter stuff on synchronizedMethod.getBody().append(finallyBlock); synchronizedMethod.addExceptionHandler(tryPosition, catchPosition, finallyStart, null/* ==finally */, false); synchronizedMethod.addExceptionHandler(finallyStart, finallyStart.getNext(), finallyStart, null, false); } else { // TRANSFORMING STATIC METHOD ON PRE JAVA5 // Hideous nightmare, class literal references prior to Java5 // YIKES! this is just the code for MONITORENTER ! // 0: getstatic #59; //Field class$1:Ljava/lang/Class; // 3: dup // 4: ifnonnull 32 // 7: pop // try // 8: ldc #61; //String java.lang.String // 10: invokestatic #44; //Method // java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class; // 13: dup // catch // 14: putstatic #59; //Field class$1:Ljava/lang/Class; // 17: goto 32 // 20: new #46; //class java/lang/NoClassDefFoundError // 23: dup_x1 // 24: swap // 25: invokevirtual #52; //Method // java/lang/Throwable.getMessage:()Ljava/lang/String; // 28: invokespecial #54; //Method // java/lang/NoClassDefFoundError."":(Ljava/lang/String;)V // 31: athrow // 32: dup <-- partTwo (branch target) // 33: astore_0 // 34: monitorenter // // plus exceptiontable entry! // 8 13 20 Class java/lang/ClassNotFoundException Type classType = BcelWorld.makeBcelType(synchronizedMethod.getEnclosingClass().getType()); Type clazzType = Type.getType(Class.class); InstructionList parttwo = new InstructionList(); parttwo.append(InstructionFactory.createDup(1)); int slotForThis = synchronizedMethod.allocateLocal(classType); parttwo.append(InstructionFactory.createStore(clazzType, slotForThis)); // ? should be the real type ? String or // something? parttwo.append(InstructionFactory.MONITORENTER); String fieldname = synchronizedMethod.getEnclosingClass().allocateField("class$"); FieldGen f = new FieldGen(Modifier.STATIC | Modifier.PRIVATE, Type.getType(Class.class), fieldname, synchronizedMethod.getEnclosingClass().getConstantPool()); synchronizedMethod.getEnclosingClass().addField(f, null); // 10: invokestatic #44; //Method // java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class; // 13: dup // 14: putstatic #59; //Field class$1:Ljava/lang/Class; // 17: goto 32 // 20: new #46; //class java/lang/NoClassDefFoundError // 23: dup_x1 // 24: swap // 25: invokevirtual #52; //Method // java/lang/Throwable.getMessage:()Ljava/lang/String; // 28: invokespecial #54; //Method // java/lang/NoClassDefFoundError."":(Ljava/lang/String;)V // 31: athrow String name = synchronizedMethod.getEnclosingClass().getName(); prepend.append(fact.createGetStatic(name, fieldname, Type.getType(Class.class))); prepend.append(InstructionFactory.createDup(1)); prepend.append(InstructionFactory.createBranchInstruction(Constants.IFNONNULL, parttwo.getStart())); prepend.append(InstructionFactory.POP); prepend.append(fact.createConstant(name)); InstructionHandle tryInstruction = prepend.getEnd(); prepend.append(fact.createInvoke("java.lang.Class", "forName", clazzType, new Type[] { Type.getType(String.class) }, Constants.INVOKESTATIC)); InstructionHandle catchInstruction = prepend.getEnd(); prepend.append(InstructionFactory.createDup(1)); prepend.append(fact.createPutStatic(synchronizedMethod.getEnclosingClass().getType().getName(), fieldname, Type.getType(Class.class))); prepend.append(InstructionFactory.createBranchInstruction(Constants.GOTO, parttwo.getStart())); // start of catch block InstructionList catchBlockForLiteralLoadingFail = new InstructionList(); catchBlockForLiteralLoadingFail.append(fact.createNew((ObjectType) Type.getType(NoClassDefFoundError.class))); catchBlockForLiteralLoadingFail.append(InstructionFactory.createDup_1(1)); catchBlockForLiteralLoadingFail.append(InstructionFactory.SWAP); catchBlockForLiteralLoadingFail.append(fact.createInvoke("java.lang.Throwable", "getMessage", Type.getType(String.class), Type.NO_ARGS, Constants.INVOKEVIRTUAL)); catchBlockForLiteralLoadingFail.append(fact.createInvoke("java.lang.NoClassDefFoundError", "", Type.VOID, new Type[] { Type.getType(String.class) }, Constants.INVOKESPECIAL)); catchBlockForLiteralLoadingFail.append(InstructionFactory.ATHROW); InstructionHandle catchBlockStart = catchBlockForLiteralLoadingFail.getStart(); prepend.append(catchBlockForLiteralLoadingFail); prepend.append(parttwo); // MONITORENTER // pseudocode: load up 'this' (var0), dup it, store it in a new // local var (for use with monitorexit) and call // monitorenter: // ALOAD_0, DUP, ASTORE_, MONITORENTER // prepend.append(InstructionFactory.createLoad(classType,0)); // prepend.append(InstructionFactory.createDup(1)); // int slotForThis = // synchronizedMethod.allocateLocal(classType); // prepend.append(InstructionFactory.createStore(classType, // slotForThis)); // prepend.append(InstructionFactory.MONITORENTER); // MONITOREXIT // here be dragons // We basically need to wrap the code from the method in a // finally block that // will ensure monitorexit is called. Content on the finally // block seems to // be always: // // E1: ALOAD_1 // MONITOREXIT // ATHROW // // so lets build that: InstructionList finallyBlock = new InstructionList(); finallyBlock.append(InstructionFactory.createLoad(Type.getType(java.lang.Class.class), slotForThis)); finallyBlock.append(InstructionConstants.MONITOREXIT); finallyBlock.append(InstructionConstants.ATHROW); // finally -> E1 // | GETSTATIC java.lang.System.out Ljava/io/PrintStream; (line // 21) // | LDC "hello" // | INVOKEVIRTUAL java.io.PrintStream.println // (Ljava/lang/String;)V // | ALOAD_1 (line 20) // | MONITOREXIT // finally -> E1 // GOTO L0 // finally -> E1 // | E1: ALOAD_1 // | MONITOREXIT // finally -> E1 // ATHROW // L0: RETURN (line 23) // frameEnv.put(donorFramePos, thisSlot); // search for 'returns' and make them to the // aload_,monitorexit InstructionHandle walker = body.getStart(); List rets = new ArrayList<>(); while (walker != null) { // !walker.equals(body.getEnd())) { if (walker.getInstruction().isReturnInstruction()) { rets.add(walker); } walker = walker.getNext(); } if (rets.size() > 0) { // need to ensure targeters for 'return' now instead target // the load instruction // (so we never jump over the monitorexit logic) for (InstructionHandle ret : rets) { // System.err.println("Adding monitor exit block at "+ // element); InstructionList monitorExitBlock = new InstructionList(); monitorExitBlock.append(InstructionFactory.createLoad(classType, slotForThis)); monitorExitBlock.append(InstructionConstants.MONITOREXIT); // monitorExitBlock.append(Utility.copyInstruction(element // .getInstruction())); // element.setInstruction(InstructionFactory.createLoad( // classType,slotForThis)); InstructionHandle monitorExitBlockStart = body.insert(ret, monitorExitBlock); // now move the targeters from the RET to the start of // the monitorexit block for (InstructionTargeter targeter : ret.getTargetersCopy()) { // what kinds are there? if (targeter instanceof LocalVariableTag) { // ignore } else if (targeter instanceof LineNumberTag) { // ignore // } else if (targeter instanceof GOTO || // targeter instanceof GOTO_W) { // // move it... // targeter.updateTarget(element, // monitorExitBlockStart); } else if (targeter instanceof InstructionBranch) { // move it targeter.updateTarget(ret, monitorExitBlockStart); } else { throw new BCException("Unexpected targeter encountered during transform: " + targeter); } } } } // body = // rewriteWithMonitorExitCalls(body,fact,true,slotForThis, // classType); // synchronizedMethod.setBody(body); // now the magic, putting the finally block around the code InstructionHandle finallyStart = finallyBlock.getStart(); InstructionHandle tryPosition = body.getStart(); InstructionHandle catchPosition = body.getEnd(); body.insert(body.getStart(), prepend); // now we can put the // monitorenter stuff on synchronizedMethod.getBody().append(finallyBlock); synchronizedMethod.addExceptionHandler(tryPosition, catchPosition, finallyStart, null/* ==finally */, false); synchronizedMethod.addExceptionHandler(tryInstruction, catchInstruction, catchBlockStart, (ObjectType) Type.getType(ClassNotFoundException.class), true); synchronizedMethod.addExceptionHandler(finallyStart, finallyStart.getNext(), finallyStart, null, false); } } else { // TRANSFORMING NON STATIC METHOD Type classType = BcelWorld.makeBcelType(synchronizedMethod.getEnclosingClass().getType()); // MONITORENTER // pseudocode: load up 'this' (var0), dup it, store it in a new // local var (for use with monitorexit) and call // monitorenter: // ALOAD_0, DUP, ASTORE_, MONITORENTER prepend.append(InstructionFactory.createLoad(classType, 0)); prepend.append(InstructionFactory.createDup(1)); int slotForThis = synchronizedMethod.allocateLocal(classType); prepend.append(InstructionFactory.createStore(classType, slotForThis)); prepend.append(InstructionFactory.MONITORENTER); // body.insert(body.getStart(),prepend); // MONITOREXIT // We basically need to wrap the code from the method in a finally // block that // will ensure monitorexit is called. Content on the finally block // seems to // be always: // // E1: ALOAD_1 // MONITOREXIT // ATHROW // // so lets build that: InstructionList finallyBlock = new InstructionList(); finallyBlock.append(InstructionFactory.createLoad(classType, slotForThis)); finallyBlock.append(InstructionConstants.MONITOREXIT); finallyBlock.append(InstructionConstants.ATHROW); // finally -> E1 // | GETSTATIC java.lang.System.out Ljava/io/PrintStream; (line 21) // | LDC "hello" // | INVOKEVIRTUAL java.io.PrintStream.println (Ljava/lang/String;)V // | ALOAD_1 (line 20) // | MONITOREXIT // finally -> E1 // GOTO L0 // finally -> E1 // | E1: ALOAD_1 // | MONITOREXIT // finally -> E1 // ATHROW // L0: RETURN (line 23) // frameEnv.put(donorFramePos, thisSlot); // search for 'returns' and make them to the aload_,monitorexit InstructionHandle walker = body.getStart(); List rets = new ArrayList<>(); while (walker != null) { // !walker.equals(body.getEnd())) { if (walker.getInstruction().isReturnInstruction()) { rets.add(walker); } walker = walker.getNext(); } if (!rets.isEmpty()) { // need to ensure targeters for 'return' now instead target the // load instruction // (so we never jump over the monitorexit logic) for (InstructionHandle element : rets) { // System.err.println("Adding monitor exit block at "+element // ); InstructionList monitorExitBlock = new InstructionList(); monitorExitBlock.append(InstructionFactory.createLoad(classType, slotForThis)); monitorExitBlock.append(InstructionConstants.MONITOREXIT); // monitorExitBlock.append(Utility.copyInstruction(element. // getInstruction())); // element.setInstruction(InstructionFactory.createLoad( // classType,slotForThis)); InstructionHandle monitorExitBlockStart = body.insert(element, monitorExitBlock); // now move the targeters from the RET to the start of the // monitorexit block for (InstructionTargeter targeter : element.getTargetersCopy()) { // what kinds are there? if (targeter instanceof LocalVariableTag) { // ignore } else if (targeter instanceof LineNumberTag) { // ignore // } else if (targeter instanceof GOTO || // targeter instanceof GOTO_W) { // // move it... // targeter.updateTarget(element, // monitorExitBlockStart); } else if (targeter instanceof InstructionBranch) { // move it targeter.updateTarget(element, monitorExitBlockStart); } else { throw new BCException("Unexpected targeter encountered during transform: " + targeter); } } } } // now the magic, putting the finally block around the code InstructionHandle finallyStart = finallyBlock.getStart(); InstructionHandle tryPosition = body.getStart(); InstructionHandle catchPosition = body.getEnd(); body.insert(body.getStart(), prepend); // now we can put the // monitorenter stuff on synchronizedMethod.getBody().append(finallyBlock); synchronizedMethod.addExceptionHandler(tryPosition, catchPosition, finallyStart, null/* ==finally */, false); synchronizedMethod.addExceptionHandler(finallyStart, finallyStart.getNext(), finallyStart, null, false); // also the exception handling for the finally block jumps to itself // max locals will already have been modified in the allocateLocal() // call // synchronized bit is removed on LazyMethodGen.pack() } // gonna have to go through and change all aload_0s to load the var from // a variable, // going to add a new variable for the this var if (trace.isTraceEnabled()) { trace.exit("transformSynchronizedMethod"); } } /** * generate the instructions to be inlined. * * @param donor the method from which we will copy (and adjust frame and jumps) instructions. * @param recipient the method the instructions will go into. Used to get the frame size so we can allocate new frame locations * for locals in donor. * @param frameEnv an environment to map from donor frame to recipient frame, initially populated with argument locations. * @param fact an instruction factory for recipient */ static InstructionList genInlineInstructions(LazyMethodGen donor, LazyMethodGen recipient, IntMap frameEnv, InstructionFactory fact, boolean keepReturns) { InstructionList footer = new InstructionList(); InstructionHandle end = footer.append(InstructionConstants.NOP); InstructionList ret = new InstructionList(); InstructionList sourceList = donor.getBody(); Map srcToDest = new HashMap<>(); ConstantPool donorCpg = donor.getEnclosingClass().getConstantPool(); ConstantPool recipientCpg = recipient.getEnclosingClass().getConstantPool(); boolean isAcrossClass = donorCpg != recipientCpg; BootstrapMethods bootstrapMethods = null; // first pass: copy the instructions directly, populate the srcToDest // map, // fix frame instructions for (InstructionHandle src = sourceList.getStart(); src != null; src = src.getNext()) { Instruction fresh = Utility.copyInstruction(src.getInstruction()); InstructionHandle dest; // OPTIMIZE optimize this stuff? if (fresh.isConstantPoolInstruction()) { // need to reset index to go to new constant pool. This is totally // a computation leak... we're testing this LOTS of times. Sigh. if (isAcrossClass) { InstructionCP cpi = (InstructionCP) fresh; cpi.setIndex(recipientCpg.addConstant(donorCpg.getConstant(cpi.getIndex()), donorCpg)); } // May need to copy bootstrapmethods across too. // if (fresh instanceof InvokeDynamic) { // InvokeDynamic id = (InvokeDynamic)fresh; // ConstantInvokeDynamic cid = (ConstantInvokeDynamic)donorCpg.getConstant(src.getInstruction().getIndex()); // int bmaIndex = cid.getBootstrapMethodAttrIndex(); // if (bootstrapMethods == null) { // Collection attributes = donor.getEnclosingClass().getAttributes(); // if (attributes != null) { // for (Attribute attribute: attributes) { // if (attribute instanceof BootstrapMethods) { // bootstrapMethods = (BootstrapMethods)attribute; // } // } // } // BootstrapMethods.BootstrapMethod bootstrapMethod = // bootstrapMethods.getBootstrapMethods()[bmaIndex]; // ConstantMethodHandle methodhandle = (ConstantMethodHandle)donorCpg.getConstant(bootstrapMethod.getBootstrapMethodRef()); // int bootstrapMethodArguments[] = bootstrapMethod.getBootstrapArguments(); // // // Finally have all we need to build the new one... // // int newMethodHandleIndex = recipientCpg.addConstant(methodhandle, donorCpg); // int[] newMethodArguments = new int[bootstrapMethodArguments.length]; // for (int a=0; a newAttributes = recipient.getEnclosingClass().getAttributes(); // BootstrapMethods newBootstrapMethods = null; // for (Attribute attr: newAttributes) { // if (attr instanceof BootstrapMethods) { // newBootstrapMethods = (BootstrapMethods)newBootstrapMethods; // } // } // if (newBootstrapMethods == null) { // newBootstrapMethods = // new BootstrapMethods(recipientCpg.addUtf8("BootstrapMethods"), // 2+newBootstrapMethod.getLength(), // new BootstrapMethods.BootstrapMethod[] {newBootstrapMethod}, // recipientCpg); // recipient.getEnclosingClass().addAttribute(newBootstrapMethods); // } // TODO need to copy over lambda$0 support methods too... // } // // } } if (src.getInstruction() == Range.RANGEINSTRUCTION) { dest = ret.append(Range.RANGEINSTRUCTION); } else if (fresh.isReturnInstruction()) { if (keepReturns) { dest = ret.append(fresh); } else { dest = ret.append(InstructionFactory.createBranchInstruction(Constants.GOTO, end)); } } else if (fresh instanceof InstructionBranch) { dest = ret.append((InstructionBranch) fresh); } else if (fresh.isLocalVariableInstruction() || fresh instanceof RET) { // IndexedInstruction indexed = (IndexedInstruction) fresh; int oldIndex = fresh.getIndex(); int freshIndex; if (!frameEnv.hasKey(oldIndex)) { freshIndex = recipient.allocateLocal(2); frameEnv.put(oldIndex, freshIndex); } else { freshIndex = frameEnv.get(oldIndex); } if (fresh instanceof RET) { fresh.setIndex(freshIndex); } else { fresh = ((InstructionLV) fresh).setIndexAndCopyIfNecessary(freshIndex); } dest = ret.append(fresh); } else { dest = ret.append(fresh); } srcToDest.put(src, dest); } // second pass: retarget branch instructions, copy ranges and tags Map tagMap = new HashMap<>(); Map shadowMap = new HashMap<>(); for (InstructionHandle dest = ret.getStart(), src = sourceList.getStart(); dest != null; dest = dest.getNext(), src = src .getNext()) { Instruction inst = dest.getInstruction(); // retarget branches if (inst instanceof InstructionBranch) { InstructionBranch branch = (InstructionBranch) inst; InstructionHandle oldTarget = branch.getTarget(); InstructionHandle newTarget = srcToDest.get(oldTarget); if (newTarget == null) { // assert this is a GOTO // this was a return instruction we previously replaced } else { branch.setTarget(newTarget); if (branch instanceof InstructionSelect) { InstructionSelect select = (InstructionSelect) branch; InstructionHandle[] oldTargets = select.getTargets(); for (int k = oldTargets.length - 1; k >= 0; k--) { select.setTarget(k, srcToDest.get(oldTargets[k])); } } } } // copy over tags and range attributes for (InstructionTargeter old : src.getTargeters()) { if (old instanceof Tag) { Tag oldTag = (Tag) old; Tag fresh = tagMap.get(oldTag); if (fresh == null) { fresh = oldTag.copy(); if (old instanceof LocalVariableTag) { // LocalVariable LocalVariableTag lvTag = (LocalVariableTag) old; LocalVariableTag lvTagFresh = (LocalVariableTag) fresh; if (lvTag.getSlot() == 0) { fresh = new LocalVariableTag(lvTag.getRealType().getSignature(), "ajc$aspectInstance", frameEnv.get(lvTag.getSlot()), 0); } else { // // Do not move it - when copying the code from the aspect to the affected target, 'this' is // // going to change from aspect to affected type. So just fix the type // System.out.println("For local variable tag at instruction " + src + " changing slot from " // + lvTag.getSlot() + " > " + frameEnv.get(lvTag.getSlot())); lvTagFresh.updateSlot(frameEnv.get(lvTag.getSlot())); } } tagMap.put(oldTag, fresh); } dest.addTargeter(fresh); } else if (old instanceof ExceptionRange) { ExceptionRange er = (ExceptionRange) old; if (er.getStart() == src) { ExceptionRange freshEr = new ExceptionRange(recipient.getBody(), er.getCatchType(), er.getPriority()); freshEr.associateWithTargets(dest, srcToDest.get(er.getEnd()), srcToDest.get(er.getHandler())); } } else if (old instanceof ShadowRange) { ShadowRange oldRange = (ShadowRange) old; if (oldRange.getStart() == src) { BcelShadow oldShadow = oldRange.getShadow(); BcelShadow freshEnclosing = oldShadow.getEnclosingShadow() == null ? null : shadowMap .get(oldShadow.getEnclosingShadow()); BcelShadow freshShadow = oldShadow.copyInto(recipient, freshEnclosing); ShadowRange freshRange = new ShadowRange(recipient.getBody()); freshRange.associateWithShadow(freshShadow); freshRange.associateWithTargets(dest, srcToDest.get(oldRange.getEnd())); shadowMap.put(oldShadow, freshShadow); // oldRange, freshRange // recipient.matchedShadows.add(freshShadow); // XXX should go through the NEW copied shadow and // update // the thisVar, targetVar, and argsVar // ??? Might want to also go through at this time and // add // "extra" vars to the shadow. } } } } if (!keepReturns) { ret.append(footer); } return ret; } // static InstructionList rewriteWithMonitorExitCalls(InstructionList // sourceList,InstructionFactory fact,boolean keepReturns,int // monitorVarSlot,Type monitorVarType) // { // InstructionList footer = new InstructionList(); // InstructionHandle end = footer.append(InstructionConstants.NOP); // // InstructionList newList = new InstructionList(); // // Map srcToDest = new HashMap(); // // // first pass: copy the instructions directly, populate the srcToDest // map, // // fix frame instructions // for (InstructionHandle src = sourceList.getStart(); src != null; src = // src.getNext()) { // Instruction fresh = Utility.copyInstruction(src.getInstruction()); // InstructionHandle dest; // if (src.getInstruction() == Range.RANGEINSTRUCTION) { // dest = newList.append(Range.RANGEINSTRUCTION); // } else if (fresh.isReturnInstruction()) { // if (keepReturns) { // newList.append(InstructionFactory.createLoad(monitorVarType,monitorVarSlot // )); // newList.append(InstructionConstants.MONITOREXIT); // dest = newList.append(fresh); // } else { // dest = // newList.append(InstructionFactory.createBranchInstruction(Constants.GOTO, // end)); // } // } else if (fresh instanceof InstructionBranch) { // dest = newList.append((InstructionBranch) fresh); // } else if ( // fresh.isLocalVariableInstruction() || fresh instanceof RET) { // //IndexedInstruction indexed = (IndexedInstruction) fresh; // int oldIndex = fresh.getIndex(); // int freshIndex; // // if (!frameEnv.hasKey(oldIndex)) { // // freshIndex = recipient.allocateLocal(2); // // frameEnv.put(oldIndex, freshIndex); // // } else { // freshIndex = oldIndex;//frameEnv.get(oldIndex); // // } // if (fresh instanceof RET) { // fresh.setIndex(freshIndex); // } else { // fresh = ((InstructionLV)fresh).setIndexAndCopyIfNecessary(freshIndex); // } // dest = newList.append(fresh); // } else { // dest = newList.append(fresh); // } // srcToDest.put(src, dest); // } // // // second pass: retarget branch instructions, copy ranges and tags // Map tagMap = new HashMap(); // for (InstructionHandle dest = newList.getStart(), src = // sourceList.getStart(); // dest != null; // dest = dest.getNext(), src = src.getNext()) { // Instruction inst = dest.getInstruction(); // // // retarget branches // if (inst instanceof InstructionBranch) { // InstructionBranch branch = (InstructionBranch) inst; // InstructionHandle oldTarget = branch.getTarget(); // InstructionHandle newTarget = // (InstructionHandle) srcToDest.get(oldTarget); // if (newTarget == null) { // // assert this is a GOTO // // this was a return instruction we previously replaced // } else { // branch.setTarget(newTarget); // if (branch instanceof InstructionSelect) { // InstructionSelect select = (InstructionSelect) branch; // InstructionHandle[] oldTargets = select.getTargets(); // for (int k = oldTargets.length - 1; k >= 0; k--) { // select.setTarget( // k, // (InstructionHandle) srcToDest.get(oldTargets[k])); // } // } // } // } // // //copy over tags and range attributes // Iterator tIter = src.getTargeters().iterator(); // // while (tIter.hasNext()) { // InstructionTargeter old = (InstructionTargeter)tIter.next(); // if (old instanceof Tag) { // Tag oldTag = (Tag) old; // Tag fresh = (Tag) tagMap.get(oldTag); // if (fresh == null) { // fresh = oldTag.copy(); // tagMap.put(oldTag, fresh); // } // dest.addTargeter(fresh); // } else if (old instanceof ExceptionRange) { // ExceptionRange er = (ExceptionRange) old; // if (er.getStart() == src) { // ExceptionRange freshEr = // new ExceptionRange(newList/*recipient.getBody()*/,er.getCatchType(),er. // getPriority()); // freshEr.associateWithTargets( // dest, // (InstructionHandle)srcToDest.get(er.getEnd()), // (InstructionHandle)srcToDest.get(er.getHandler())); // } // } // /*else if (old instanceof ShadowRange) { // ShadowRange oldRange = (ShadowRange) old; // if (oldRange.getStart() == src) { // BcelShadow oldShadow = oldRange.getShadow(); // BcelShadow freshEnclosing = // oldShadow.getEnclosingShadow() == null // ? null // : (BcelShadow) shadowMap.get(oldShadow.getEnclosingShadow()); // BcelShadow freshShadow = // oldShadow.copyInto(recipient, freshEnclosing); // ShadowRange freshRange = new ShadowRange(recipient.getBody()); // freshRange.associateWithShadow(freshShadow); // freshRange.associateWithTargets( // dest, // (InstructionHandle) srcToDest.get(oldRange.getEnd())); // shadowMap.put(oldRange, freshRange); // //recipient.matchedShadows.add(freshShadow); // // XXX should go through the NEW copied shadow and update // // the thisVar, targetVar, and argsVar // // ??? Might want to also go through at this time and add // // "extra" vars to the shadow. // } // }*/ // } // } // if (!keepReturns) newList.append(footer); // return newList; // } /** * generate the argument stores in preparation for inlining. * * @param donor the method we will inline from. Used to get the signature. * @param recipient the method we will inline into. Used to get the frame size so we can allocate fresh locations. * @param frameEnv an empty environment we populate with a map from donor frame to recipient frame. * @param fact an instruction factory for recipient */ private static InstructionList genArgumentStores(LazyMethodGen donor, LazyMethodGen recipient, IntMap frameEnv, InstructionFactory fact) { InstructionList ret = new InstructionList(); int donorFramePos = 0; // writing ret back to front because we're popping. if (!donor.isStatic()) { int targetSlot = recipient.allocateLocal(Type.OBJECT); ret.insert(InstructionFactory.createStore(Type.OBJECT, targetSlot)); frameEnv.put(donorFramePos, targetSlot); donorFramePos += 1; } Type[] argTypes = donor.getArgumentTypes(); for (Type argType : argTypes) { int argSlot = recipient.allocateLocal(argType); ret.insert(InstructionFactory.createStore(argType, argSlot)); frameEnv.put(donorFramePos, argSlot); donorFramePos += argType.getSize(); } return ret; } /** * get a called method: Assumes the called method is in this class, and the reference to it is exact (a la INVOKESPECIAL). * * @param ih The InvokeInstruction instructionHandle pointing to the called method. */ private LazyMethodGen getCalledMethod(InstructionHandle ih) { InvokeInstruction inst = (InvokeInstruction) ih.getInstruction(); String methodName = inst.getName(cpg); String signature = inst.getSignature(cpg); return clazz.getLazyMethodGen(methodName, signature); } private void weaveInAddedMethods() { addedLazyMethodGens.sort(new Comparator() { public int compare(LazyMethodGen aa, LazyMethodGen bb) { int i = aa.getName().compareTo(bb.getName()); if (i != 0) { return i; } return aa.getSignature().compareTo(bb.getSignature()); } }); for (LazyMethodGen addedMember : addedLazyMethodGens) { clazz.addMethodGen(addedMember); } } // void addPerSingletonField(Member field) { // ObjectType aspectType = (ObjectType) // BcelWorld.makeBcelType(field.getReturnType()); // String aspectName = field.getReturnType().getName(); // // LazyMethodGen clinit = clazz.getStaticInitializer(); // InstructionList setup = new InstructionList(); // InstructionFactory fact = clazz.getFactory(); // // setup.append(fact.createNew(aspectType)); // setup.append(InstructionFactory.createDup(1)); // setup.append(fact.createInvoke(aspectName, "", Type.VOID, new // Type[0], Constants.INVOKESPECIAL)); // setup.append(fact.createFieldAccess(aspectName, field.getName(), // aspectType, Constants.PUTSTATIC)); // clinit.getBody().insert(setup); // } /** * Returns null if this is not a Java constructor, and then we won't weave into it at all */ private InstructionHandle findSuperOrThisCall(LazyMethodGen mg) { int depth = 1; InstructionHandle start = mg.getBody().getStart(); while (true) { if (start == null) { return null; } Instruction inst = start.getInstruction(); if (inst.opcode == Constants.INVOKESPECIAL && ((InvokeInstruction) inst).getName(cpg).equals("")) { depth--; if (depth == 0) { return start; } } else if (inst.opcode == Constants.NEW) { depth++; } start = start.getNext(); } } // ---- private boolean match(LazyMethodGen mg) { BcelShadow enclosingShadow; List shadowAccumulator = new ArrayList<>(); boolean isOverweaving = world.isOverWeaving(); boolean startsAngly = mg.getName().charAt(0) == '<'; // we want to match ajsynthetic constructors... if (startsAngly && mg.getName().equals("")) { return matchInit(mg, shadowAccumulator); } else if (!shouldWeaveBody(mg)) { return false; } else { if (startsAngly && mg.getName().equals("")) { // clinitShadow = enclosingShadow = BcelShadow.makeStaticInitialization(world, mg); // System.err.println(enclosingShadow); } else if (mg.isAdviceMethod()) { enclosingShadow = BcelShadow.makeAdviceExecution(world, mg); } else { AjAttribute.EffectiveSignatureAttribute effective = mg.getEffectiveSignature(); if (effective == null) { // Don't want ajc$preClinit to be considered for matching if (isOverweaving && mg.getName().startsWith(NameMangler.PREFIX)) { return false; } if (mg.getName().startsWith(SWITCH_TABLE_SYNTHETIC_METHOD_PREFIX) && Objects.equals(mg.getReturnType().getSignature(), "[I")) { // this is a synthetic switch helper, should be skipped (since it's not 'declared') return false; } enclosingShadow = BcelShadow.makeMethodExecution(world, mg, !canMatchBodyShadows); } else if (effective.isWeaveBody()) { ResolvedMember rm = effective.getEffectiveSignature(); // Annotations for things with effective signatures are // never stored in the effective // signature itself - we have to hunt for them. Storing them // in the effective signature // would mean keeping two sets up to date (no way!!) fixParameterNamesForResolvedMember(rm, mg.getMemberView()); fixAnnotationsForResolvedMember(rm, mg.getMemberView()); enclosingShadow = BcelShadow.makeShadowForMethod(world, mg, effective.getShadowKind(), rm); } else { return false; } } if (canMatchBodyShadows) { for (InstructionHandle h = mg.getBody().getStart(); h != null; h = h.getNext()) { match(mg, h, enclosingShadow, shadowAccumulator); } } // FIXME asc change from string match if we can, rather brittle. // this check actually prevents field-exec jps if (canMatch(enclosingShadow.getKind()) && !(mg.getName().charAt(0) == 'a' && mg.getName().startsWith("ajc$interFieldInit"))) { if (match(enclosingShadow, shadowAccumulator)) { enclosingShadow.init(); } } mg.matchedShadows = shadowAccumulator; return !shadowAccumulator.isEmpty(); } } private boolean matchInit(LazyMethodGen mg, List shadowAccumulator) { BcelShadow enclosingShadow; // XXX the enclosing join point is wrong for things before ignoreMe. InstructionHandle superOrThisCall = findSuperOrThisCall(mg); // we don't walk bodies of things where it's a wrong constructor thingie if (superOrThisCall == null) { return false; } enclosingShadow = BcelShadow.makeConstructorExecution(world, mg, superOrThisCall); if (mg.getEffectiveSignature() != null) { enclosingShadow.setMatchingSignature(mg.getEffectiveSignature().getEffectiveSignature()); } // walk the body boolean beforeSuperOrThisCall = true; if (shouldWeaveBody(mg)) { if (canMatchBodyShadows) { for (InstructionHandle h = mg.getBody().getStart(); h != null; h = h.getNext()) { if (h == superOrThisCall) { beforeSuperOrThisCall = false; continue; } match(mg, h, beforeSuperOrThisCall ? null : enclosingShadow, shadowAccumulator); } } if (canMatch(Shadow.ConstructorExecution)) { match(enclosingShadow, shadowAccumulator); } } // XXX we don't do pre-inits of interfaces // now add interface inits if (!isThisCall(superOrThisCall)) { InstructionHandle curr = enclosingShadow.getRange().getStart(); for (IfaceInitList l : addedSuperInitializersAsList) { Member ifaceInitSig = AjcMemberMaker.interfaceConstructor(l.onType); BcelShadow initShadow = BcelShadow.makeIfaceInitialization(world, mg, ifaceInitSig); // insert code in place InstructionList inits = genInitInstructions(l.list, false); if (match(initShadow, shadowAccumulator) || !inits.isEmpty()) { initShadow.initIfaceInitializer(curr); initShadow.getRange().insert(inits, Range.OutsideBefore); } } // now we add our initialization code InstructionList inits = genInitInstructions(addedThisInitializers, false); enclosingShadow.getRange().insert(inits, Range.OutsideBefore); } // actually, you only need to inline the self constructors that are // in a particular group (partition the constructors into groups where // members // call or are called only by those in the group). Then only inline // constructors // in groups where at least one initialization jp matched. Future work. boolean addedInitialization = match(BcelShadow.makeUnfinishedInitialization(world, mg), initializationShadows); addedInitialization |= match(BcelShadow.makeUnfinishedPreinitialization(world, mg), initializationShadows); mg.matchedShadows = shadowAccumulator; return addedInitialization || !shadowAccumulator.isEmpty(); } private boolean shouldWeaveBody(LazyMethodGen mg) { if (mg.isBridgeMethod()) { return false; } if (mg.isAjSynthetic()) { return mg.getName().equals(""); } AjAttribute.EffectiveSignatureAttribute a = mg.getEffectiveSignature(); if (a != null) { return a.isWeaveBody(); } return true; } /** * first sorts the mungers, then gens the initializers in the right order */ private InstructionList genInitInstructions(List list, boolean isStatic) { list = PartialOrder.sort(list); if (list == null) { throw new BCException("circularity in inter-types"); } InstructionList ret = new InstructionList(); for (ConcreteTypeMunger cmunger : list) { NewFieldTypeMunger munger = (NewFieldTypeMunger) cmunger.getMunger(); ResolvedMember initMethod = munger.getInitMethod(cmunger.getAspectType()); if (!isStatic) { ret.append(InstructionConstants.ALOAD_0); } ret.append(Utility.createInvoke(fact, world, initMethod)); } return ret; } private void match(LazyMethodGen mg, InstructionHandle ih, BcelShadow enclosingShadow, List shadowAccumulator) { Instruction i = ih.getInstruction(); // Exception handlers (pr230817) if (canMatch(Shadow.ExceptionHandler) && !Range.isRangeHandle(ih)) { Set targeters = ih.getTargetersCopy(); // If in Java7 there may be overlapping exception ranges for multi catch - we should recognize that for (InstructionTargeter t : targeters) { if (t instanceof ExceptionRange) { // assert t.getHandler() == ih ExceptionRange er = (ExceptionRange) t; if (er.getCatchType() == null) { continue; } if (isInitFailureHandler(ih)) { return; } if (!ih.getInstruction().isStoreInstruction() && ih.getInstruction().getOpcode() != Constants.NOP) { // If using cobertura, the catch block stats with // INVOKESTATIC rather than ASTORE, in order that the ranges // for the methodcall and exceptionhandler shadows // that occur at this same // line, we need to modify the instruction list to // split them - adding a // NOP before the invokestatic that gets all the targeters // that were aimed at the INVOKESTATIC mg.getBody().insert(ih, InstructionConstants.NOP); InstructionHandle newNOP = ih.getPrev(); // what about a try..catch that starts at the start // of the exception handler? need to only include // certain targeters really. er.updateTarget(ih, newNOP, mg.getBody()); for (InstructionTargeter t2 : targeters) { newNOP.addTargeter(t2); } ih.removeAllTargeters(); match(BcelShadow.makeExceptionHandler(world, er, mg, newNOP, enclosingShadow), shadowAccumulator); } else { match(BcelShadow.makeExceptionHandler(world, er, mg, ih, enclosingShadow), shadowAccumulator); } } } } if ((i instanceof FieldInstruction) && (canMatch(Shadow.FieldGet) || canMatch(Shadow.FieldSet))) { FieldInstruction fi = (FieldInstruction) i; if (fi.opcode == Constants.PUTFIELD || fi.opcode == Constants.PUTSTATIC) { // check for sets of constant fields. We first check the // previous // instruction. If the previous instruction is a LD_WHATEVER // (push // constant on the stack) then we must resolve the field to // determine // if it's final. If it is final, then we don't generate a // shadow. InstructionHandle prevHandle = ih.getPrev(); Instruction prevI = prevHandle.getInstruction(); if (Utility.isConstantPushInstruction(prevI)) { Member field = BcelWorld.makeFieldJoinPointSignature(clazz, (FieldInstruction) i); ResolvedMember resolvedField = field.resolve(world); if (resolvedField == null) { // we can't find the field, so it's not a join point. } else if (Modifier.isFinal(resolvedField.getModifiers())) { // it's final, so it's the set of a final constant, so // it's // not a join point according to 1.0.6 and 1.1. } else { if (canMatch(Shadow.FieldSet)) { matchSetInstruction(mg, ih, enclosingShadow, shadowAccumulator); } } } else { if (canMatch(Shadow.FieldSet)) { matchSetInstruction(mg, ih, enclosingShadow, shadowAccumulator); } } } else { if (canMatch(Shadow.FieldGet)) { matchGetInstruction(mg, ih, enclosingShadow, shadowAccumulator); } } } else if (i instanceof InvokeInstruction) { InvokeInstruction ii = (InvokeInstruction) i; if (ii.getMethodName(clazz.getConstantPool()).equals("")) { if (canMatch(Shadow.ConstructorCall)) { match(BcelShadow.makeConstructorCall(world, mg, ih, enclosingShadow), shadowAccumulator); } } else if (ii.opcode == Constants.INVOKESPECIAL) { String onTypeName = ii.getClassName(cpg); if (onTypeName.equals(mg.getEnclosingClass().getName())) { // we are private matchInvokeInstruction(mg, ih, ii, enclosingShadow, shadowAccumulator); } else { // we are a super call, and this is not a join point in // AspectJ-1.{0,1} } } else { if (ii.getOpcode()!=Constants.INVOKEDYNAMIC) { matchInvokeInstruction(mg, ih, ii, enclosingShadow, shadowAccumulator); } } } else if (world.isJoinpointArrayConstructionEnabled() && i.isArrayCreationInstruction()) { if (canMatch(Shadow.ConstructorCall)) { if (i.opcode == Constants.ANEWARRAY) { // ANEWARRAY arrayInstruction = (ANEWARRAY)i; // ObjectType arrayType = i.getLoadClassType(clazz.getConstantPool()); BcelShadow ctorCallShadow = BcelShadow.makeArrayConstructorCall(world, mg, ih, enclosingShadow); match(ctorCallShadow, shadowAccumulator); } else if (i.opcode == Constants.NEWARRAY) { // NEWARRAY arrayInstruction = (NEWARRAY)i; // Type arrayType = i.getType(); BcelShadow ctorCallShadow = BcelShadow.makeArrayConstructorCall(world, mg, ih, enclosingShadow); match(ctorCallShadow, shadowAccumulator); } else if (i instanceof MULTIANEWARRAY) { // MULTIANEWARRAY arrayInstruction = (MULTIANEWARRAY) i; // ObjectType arrayType = arrayInstruction.getLoadClassType(clazz.getConstantPool()); BcelShadow ctorCallShadow = BcelShadow.makeArrayConstructorCall(world, mg, ih, enclosingShadow); match(ctorCallShadow, shadowAccumulator); } } // see pr77166 if you are thinking about implementing this // } else if (i instanceof AALOAD ) { // AALOAD arrayLoad = (AALOAD)i; // Type arrayType = arrayLoad.getType(clazz.getConstantPoolGen()); // BcelShadow arrayLoadShadow = // BcelShadow.makeArrayLoadCall(world,mg,ih,enclosingShadow); // match(arrayLoadShadow,shadowAccumulator); // } else if (i instanceof AASTORE) { // // ... magic required } else if (world.isJoinpointSynchronizationEnabled() && ((i.getOpcode() == Constants.MONITORENTER) || (i.getOpcode() == Constants.MONITOREXIT))) { // if (canMatch(Shadow.Monitoring)) { if (i.getOpcode() == Constants.MONITORENTER) { BcelShadow monitorEntryShadow = BcelShadow.makeMonitorEnter(world, mg, ih, enclosingShadow); match(monitorEntryShadow, shadowAccumulator); } else { BcelShadow monitorExitShadow = BcelShadow.makeMonitorExit(world, mg, ih, enclosingShadow); match(monitorExitShadow, shadowAccumulator); } // } } } private boolean isInitFailureHandler(InstructionHandle ih) { // Skip the astore_0 and aload_0 at the start of the handler and // then check if the instruction following these is // 'putstatic ajc$initFailureCause'. If it is then we are // in the handler we created in AspectClinit.generatePostSyntheticCode() InstructionHandle twoInstructionsAway = ih.getNext().getNext(); if (twoInstructionsAway.getInstruction().opcode == Constants.PUTSTATIC) { String name = ((FieldInstruction) twoInstructionsAway.getInstruction()).getFieldName(cpg); if (name.equals(NameMangler.INITFAILURECAUSE_FIELD_NAME)) { return true; } } return false; } private void matchSetInstruction(LazyMethodGen mg, InstructionHandle ih, BcelShadow enclosingShadow, List shadowAccumulator) { FieldInstruction fi = (FieldInstruction) ih.getInstruction(); Member field = BcelWorld.makeFieldJoinPointSignature(clazz, fi); // synthetic fields are never join points if (field.getName().startsWith(NameMangler.PREFIX)) { return; } ResolvedMember resolvedField = field.resolve(world); if (resolvedField == null) { // we can't find the field, so it's not a join point. return; } else if (Modifier.isFinal(resolvedField.getModifiers()) && Utility.isConstantPushInstruction(ih.getPrev().getInstruction())) { // it's the set of a final constant, so it's // not a join point according to 1.0.6 and 1.1. return; } else if (resolvedField.isSynthetic()) { // sets of synthetics aren't join points in 1.1 return; } else { // Fix for bug 172107 (similar the "get" fix for bug 109728) BcelShadow bs = BcelShadow.makeFieldSet(world, resolvedField, mg, ih, enclosingShadow); String cname = fi.getClassName(cpg); if (!resolvedField.getDeclaringType().getName().equals(cname)) { bs.setActualTargetType(cname); } match(bs, shadowAccumulator); } } private void matchGetInstruction(LazyMethodGen mg, InstructionHandle ih, BcelShadow enclosingShadow, List shadowAccumulator) { FieldInstruction fi = (FieldInstruction) ih.getInstruction(); Member field = BcelWorld.makeFieldJoinPointSignature(clazz, fi); // synthetic fields are never join points if (field.getName().startsWith(NameMangler.PREFIX)) { return; } ResolvedMember resolvedField = field.resolve(world); if (resolvedField == null) { // we can't find the field, so it's not a join point. return; } else if (resolvedField.isSynthetic()) { // sets of synthetics aren't join points in 1.1 return; } else { BcelShadow bs = BcelShadow.makeFieldGet(world, resolvedField, mg, ih, enclosingShadow); String cname = fi.getClassName(cpg); if (!resolvedField.getDeclaringType().getName().equals(cname)) { bs.setActualTargetType(cname); } match(bs, shadowAccumulator); } } /** * For some named resolved type, this method looks for a member with a particular name - it should only be used when you truly * believe there is only one member with that name in the type as it returns the first one it finds. */ private ResolvedMember findResolvedMemberNamed(ResolvedType type, String methodName) { ResolvedMember[] allMethods = type.getDeclaredMethods(); for (ResolvedMember member : allMethods) { if (member.getName().equals(methodName)) { return member; } } return null; } /** * Find the specified member in the specified type. * * @param type the type to search for the member * @param methodName the name of the method to find * @param params the method parameters that the discovered method should have */ private ResolvedMember findResolvedMemberNamed(ResolvedType type, String methodName, UnresolvedType[] params) { ResolvedMember[] allMethods = type.getDeclaredMethods(); List candidates = new ArrayList<>(); for (ResolvedMember candidate : allMethods) { if (candidate.getName().equals(methodName)) { if (candidate.getArity() == params.length) { candidates.add(candidate); } } } if (candidates.size() == 0) { return null; } else if (candidates.size() == 1) { return candidates.get(0); } else { // multiple candidates for (ResolvedMember candidate : candidates) { // These checks will break down with generics... but that would need two ITDs with the same name, same arity and // generics boolean allOK = true; UnresolvedType[] candidateParams = candidate.getParameterTypes(); for (int p = 0; p < candidateParams.length; p++) { if (!candidateParams[p].getErasureSignature().equals(params[p].getErasureSignature())) { allOK = false; break; } } if (allOK) { return candidate; } } } return null; } /** * For a given resolvedmember, this will discover the real annotations for it. Should only be used when the resolvedmember is * the contents of an effective signature attribute, as thats the only time when the annotations aren't stored directly in the * resolvedMember * * @param rm the sig we want it to pretend to be 'int A.m()' or somesuch ITD like thing * @param declaredSig the real sig 'blah.ajc$xxx' */ private void fixParameterNamesForResolvedMember(ResolvedMember rm, ResolvedMember declaredSig) { UnresolvedType memberHostType = declaredSig.getDeclaringType(); String methodName = declaredSig.getName(); String[] pnames = null; if (rm.getKind() == Member.METHOD && !rm.isAbstract()) { if (methodName.startsWith("ajc$inlineAccessMethod") || methodName.startsWith("ajc$superDispatch")) { ResolvedMember resolvedDooberry = world.resolve(declaredSig); pnames = resolvedDooberry.getParameterNames(); } else { ResolvedMember realthing = AjcMemberMaker.interMethodDispatcher(rm.resolve(world), memberHostType).resolve(world); ResolvedMember theRealMember = findResolvedMemberNamed(memberHostType.resolve(world), realthing.getName()); if (theRealMember != null) { pnames = theRealMember.getParameterNames(); // static ITDs don't need any parameter shifting if (pnames.length > 0 && pnames[0].equals("ajc$this_")) { String[] pnames2 = new String[pnames.length - 1]; System.arraycopy(pnames, 1, pnames2, 0, pnames2.length); pnames = pnames2; } } } // i think ctors are missing from here... copy code from below... } rm.setParameterNames(pnames); } /** * For a given resolvedmember, this will discover the real annotations for it. Should only be used when the resolvedmember is * the contents of an effective signature attribute, as thats the only time when the annotations aren't stored directly in the * resolvedMember * * @param rm the sig we want it to pretend to be 'int A.m()' or somesuch ITD like thing * @param declaredSig the real sig 'blah.ajc$xxx' */ private void fixAnnotationsForResolvedMember(ResolvedMember rm, ResolvedMember declaredSig) { try { UnresolvedType memberHostType = declaredSig.getDeclaringType(); boolean containsKey = mapToAnnotationHolder.containsKey(rm); ResolvedMember realAnnotationHolder = mapToAnnotationHolder.get(rm); String methodName = declaredSig.getName(); // FIXME asc shouldnt really rely on string names ! if (!containsKey) { if (rm.getKind() == Member.FIELD) { if (methodName.startsWith("ajc$inlineAccessField")) { realAnnotationHolder = world.resolve(rm); } else { ResolvedMember realthing = AjcMemberMaker.interFieldInitializer(rm, memberHostType); realAnnotationHolder = world.resolve(realthing); } } else if (rm.getKind() == Member.METHOD && !rm.isAbstract()) { if (methodName.startsWith("ajc$inlineAccessMethod") || methodName.startsWith("ajc$superDispatch")) { realAnnotationHolder = world.resolve(declaredSig); } else { ResolvedMember realthing = AjcMemberMaker.interMethodDispatcher(rm.resolve(world), memberHostType).resolve(world); realAnnotationHolder = findResolvedMemberNamed(memberHostType.resolve(world), realthing.getName(),realthing.getParameterTypes()); if (realAnnotationHolder == null) { throw new UnsupportedOperationException( "Known limitation in M4 - can't find ITD members when type variable is used as an argument and has upper bound specified"); } } } else if (rm.getKind() == Member.CONSTRUCTOR) { ResolvedMember realThing = AjcMemberMaker.postIntroducedConstructor(memberHostType.resolve(world),rm.getDeclaringType(), rm.getParameterTypes()); realAnnotationHolder = world.resolve(realThing); // AMC temp guard for M4 if (realAnnotationHolder == null) { throw new UnsupportedOperationException("Known limitation in M4 - can't find ITD members when type variable is used as an argument and has upper bound specified"); } } mapToAnnotationHolder.put(rm, realAnnotationHolder); } ResolvedType[] annotationTypes; AnnotationAJ[] annotations; if (realAnnotationHolder!=null) { annotationTypes = realAnnotationHolder.getAnnotationTypes(); annotations = realAnnotationHolder.getAnnotations(); if (annotationTypes==null) { annotationTypes = ResolvedType.EMPTY_ARRAY; } if (annotations==null) { annotations = AnnotationAJ.EMPTY_ARRAY; } } else { annotations = AnnotationAJ.EMPTY_ARRAY; annotationTypes = ResolvedType.EMPTY_ARRAY; } rm.setAnnotations(annotations); rm.setAnnotationTypes(annotationTypes); } catch (UnsupportedOperationException ex) { throw ex; } catch (Throwable t) { // FIXME asc remove this catch after more testing has confirmed the // above stuff is OK throw new BCException("Unexpectedly went bang when searching for annotations on " + rm, t); } } private void matchInvokeInstruction(LazyMethodGen mg, InstructionHandle ih, InvokeInstruction invoke, BcelShadow enclosingShadow, List shadowAccumulator) { String methodName = invoke.getName(cpg); if (methodName.startsWith(NameMangler.PREFIX)) { Member jpSig = world.makeJoinPointSignatureForMethodInvocation(clazz, invoke); ResolvedMember declaredSig = jpSig.resolve(world); // System.err.println(method + ", declaredSig: " +declaredSig); if (declaredSig == null) { return; } if (declaredSig.getKind() == Member.FIELD) { Shadow.Kind kind; if (jpSig.getReturnType().equals(UnresolvedType.VOID)) { kind = Shadow.FieldSet; } else { kind = Shadow.FieldGet; } if (canMatch(Shadow.FieldGet) || canMatch(Shadow.FieldSet)) { match(BcelShadow.makeShadowForMethodCall(world, mg, ih, enclosingShadow, kind, declaredSig), shadowAccumulator); } } else if (!declaredSig.getName().startsWith(NameMangler.PREFIX)) { // 307147 - resolution above may have found the real method directly rather // than needing to go through the effective signature attribute if (canMatch(Shadow.MethodCall)) { match(BcelShadow.makeShadowForMethodCall(world, mg, ih, enclosingShadow, Shadow.MethodCall, declaredSig), shadowAccumulator); } } else { AjAttribute.EffectiveSignatureAttribute effectiveSig = declaredSig.getEffectiveSignature(); if (effectiveSig == null) { return; } // System.err.println("call to inter-type member: " + // effectiveSig); if (effectiveSig.isWeaveBody()) { return; } ResolvedMember rm = effectiveSig.getEffectiveSignature(); fixParameterNamesForResolvedMember(rm, declaredSig); fixAnnotationsForResolvedMember(rm, declaredSig); // abracadabra if (canMatch(effectiveSig.getShadowKind())) { match(BcelShadow.makeShadowForMethodCall(world, mg, ih, enclosingShadow, effectiveSig.getShadowKind(), rm), shadowAccumulator); } } } else { if (canMatch(Shadow.MethodCall)) { boolean proceed = true; // overweaving needs to ignore some calls added by the previous weave if (world.isOverWeaving()) { String s = invoke.getClassName(mg.getConstantPool()); // skip all the inc/dec/isValid/etc if (s.length() > 4 && s.charAt(4) == 'a' && (s.equals("org.aspectj.runtime.internal.CFlowCounter") || s.equals("org.aspectj.runtime.internal.CFlowStack") || s .equals("org.aspectj.runtime.reflect.Factory"))) { proceed = false; } else { if (methodName.equals("aspectOf")) { proceed = false; } } } if (methodName.startsWith(SWITCH_TABLE_SYNTHETIC_METHOD_PREFIX)) { proceed = false; } if (proceed) { match(BcelShadow.makeMethodCall(world, mg, ih, enclosingShadow), shadowAccumulator); } } } } // static ... so all worlds will share the config for the first one // created... private static boolean checkedXsetForLowLevelContextCapturing = false; private static boolean captureLowLevelContext = false; private boolean match(BcelShadow shadow, List shadowAccumulator) { // Duplicate blocks - one with context one without, seems faster than multiple 'ifs' if (captureLowLevelContext) { ContextToken shadowMatchToken = CompilationAndWeavingContext.enteringPhase( CompilationAndWeavingContext.MATCHING_SHADOW, shadow); boolean isMatched = false; Shadow.Kind shadowKind = shadow.getKind(); List candidateMungers = indexedShadowMungers[shadowKind.getKey()]; // System.out.println("Candidates " + candidateMungers); if (candidateMungers != null) { for (ShadowMunger munger : candidateMungers) { ContextToken mungerMatchToken = CompilationAndWeavingContext.enteringPhase( CompilationAndWeavingContext.MATCHING_POINTCUT, munger.getPointcut()); if (munger.match(shadow, world)) { shadow.addMunger(munger); isMatched = true; if (shadow.getKind() == Shadow.StaticInitialization) { clazz.warnOnAddedStaticInitializer(shadow, munger.getSourceLocation()); } } CompilationAndWeavingContext.leavingPhase(mungerMatchToken); } if (isMatched) { shadowAccumulator.add(shadow); } } CompilationAndWeavingContext.leavingPhase(shadowMatchToken); return isMatched; } else { boolean isMatched = false; Shadow.Kind shadowKind = shadow.getKind(); List candidateMungers = indexedShadowMungers[shadowKind.getKey()]; // System.out.println("Candidates at " + shadowKind + " are " + candidateMungers); if (candidateMungers != null) { for (ShadowMunger munger : candidateMungers) { if (munger.match(shadow, world)) { shadow.addMunger(munger); isMatched = true; if (shadow.getKind() == Shadow.StaticInitialization) { clazz.warnOnAddedStaticInitializer(shadow, munger.getSourceLocation()); } } } if (isMatched) { shadowAccumulator.add(shadow); } } return isMatched; } } // ---- private void implement(LazyMethodGen mg) { List shadows = mg.matchedShadows; if (shadows == null) { return; } // We depend on a partial order such that inner shadows are earlier on // the list than outer shadows. That's fine. This order is preserved if: // A preceeds B iff B.getStart() is LATER THAN A.getStart(). for (BcelShadow shadow : shadows) { ContextToken tok = CompilationAndWeavingContext.enteringPhase(CompilationAndWeavingContext.IMPLEMENTING_ON_SHADOW, shadow); shadow.implement(); CompilationAndWeavingContext.leavingPhase(tok); } // int ii = mg.getMaxLocals(); mg.matchedShadows = null; } // ---- public LazyClassGen getLazyClassGen() { return clazz; } public BcelWorld getWorld() { return world; } public void setReweavableMode(boolean mode) { inReweavableMode = mode; } public boolean getReweavableMode() { return inReweavableMode; } @Override public String toString() { return "BcelClassWeaver instance for : " + clazz; } }