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

package org.aspectj.weaver.bcel;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.aspectj.apache.bcel.Constants;
import org.aspectj.apache.bcel.classfile.ClassParser;
import org.aspectj.apache.bcel.classfile.ConstantPool;
import org.aspectj.apache.bcel.classfile.JavaClass;
import org.aspectj.apache.bcel.generic.FieldInstruction;
import org.aspectj.apache.bcel.generic.INVOKEINTERFACE;
import org.aspectj.apache.bcel.generic.Instruction;
import org.aspectj.apache.bcel.generic.InstructionHandle;
import org.aspectj.apache.bcel.generic.InvokeInstruction;
import org.aspectj.apache.bcel.generic.MULTIANEWARRAY;
import org.aspectj.apache.bcel.generic.ObjectType;
import org.aspectj.apache.bcel.generic.Type;
import org.aspectj.apache.bcel.util.ClassLoaderReference;
import org.aspectj.apache.bcel.util.ClassLoaderRepository;
import org.aspectj.apache.bcel.util.ClassPath;
import org.aspectj.apache.bcel.util.NonCachingClassLoaderRepository;
import org.aspectj.apache.bcel.util.Repository;
import org.aspectj.asm.AsmManager;
import org.aspectj.asm.IRelationship;
import org.aspectj.asm.internal.CharOperation;
import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.IMessageHandler;
import org.aspectj.bridge.ISourceLocation;
import org.aspectj.bridge.Message;
import org.aspectj.bridge.MessageUtil;
import org.aspectj.bridge.WeaveMessage;
import org.aspectj.weaver.Advice;
import org.aspectj.weaver.AdviceKind;
import org.aspectj.weaver.AnnotationAJ;
import org.aspectj.weaver.AnnotationOnTypeMunger;
import org.aspectj.weaver.BCException;
import org.aspectj.weaver.Checker;
import org.aspectj.weaver.ICrossReferenceHandler;
import org.aspectj.weaver.IWeavingSupport;
import org.aspectj.weaver.Member;
import org.aspectj.weaver.MemberImpl;
import org.aspectj.weaver.MemberKind;
import org.aspectj.weaver.NewParentTypeMunger;
import org.aspectj.weaver.ReferenceType;
import org.aspectj.weaver.ReferenceTypeDelegate;
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.World;
import org.aspectj.weaver.loadtime.definition.Definition;
import org.aspectj.weaver.loadtime.definition.DocumentParser;
import org.aspectj.weaver.model.AsmRelationshipProvider;
import org.aspectj.weaver.patterns.DeclareAnnotation;
import org.aspectj.weaver.patterns.DeclareParents;
import org.aspectj.weaver.patterns.ParserException;
import org.aspectj.weaver.patterns.PatternParser;
import org.aspectj.weaver.patterns.TypePattern;
import org.aspectj.weaver.tools.Trace;
import org.aspectj.weaver.tools.TraceFactory;

public class BcelWorld extends World implements Repository {

	private final ClassPathManager classPath;
	protected Repository delegate;
	private BcelWeakClassLoaderReference loaderRef;
	private final BcelWeavingSupport bcelWeavingSupport = new BcelWeavingSupport();
	private boolean isXmlConfiguredWorld = false;
	private WeavingXmlConfig xmlConfiguration;
	private List<TypeDelegateResolver> typeDelegateResolvers;

	private static Trace trace = TraceFactory.getTraceFactory().getTrace(BcelWorld.class);

	public BcelWorld() {
		this("");
	}

	public BcelWorld(String cp) {
		this(makeDefaultClasspath(cp), IMessageHandler.THROW, null);
	}

	public IRelationship.Kind determineRelKind(ShadowMunger munger) {
		AdviceKind ak = ((Advice) munger).getKind();
		if (ak.getKey() == AdviceKind.Before.getKey()) {
			return IRelationship.Kind.ADVICE_BEFORE;
		} else if (ak.getKey() == AdviceKind.After.getKey()) {
			return IRelationship.Kind.ADVICE_AFTER;
		} else if (ak.getKey() == AdviceKind.AfterThrowing.getKey()) {
			return IRelationship.Kind.ADVICE_AFTERTHROWING;
		} else if (ak.getKey() == AdviceKind.AfterReturning.getKey()) {
			return IRelationship.Kind.ADVICE_AFTERRETURNING;
		} else if (ak.getKey() == AdviceKind.Around.getKey()) {
			return IRelationship.Kind.ADVICE_AROUND;
		} else if (ak.getKey() == AdviceKind.CflowEntry.getKey() || ak.getKey() == AdviceKind.CflowBelowEntry.getKey()
				|| ak.getKey() == AdviceKind.InterInitializer.getKey() || ak.getKey() == AdviceKind.PerCflowEntry.getKey()
				|| ak.getKey() == AdviceKind.PerCflowBelowEntry.getKey() || ak.getKey() == AdviceKind.PerThisEntry.getKey()
				|| ak.getKey() == AdviceKind.PerTargetEntry.getKey() || ak.getKey() == AdviceKind.Softener.getKey()
				|| ak.getKey() == AdviceKind.PerTypeWithinEntry.getKey()) {
			// System.err.println("Dont want a message about this: "+ak);
			return null;
		}
		throw new RuntimeException("Shadow.determineRelKind: What the hell is it? " + ak);
	}

	@Override
	public void reportMatch(ShadowMunger munger, Shadow shadow) {
		if (getCrossReferenceHandler() != null) {
			getCrossReferenceHandler().addCrossReference(munger.getSourceLocation(), // What is being applied
					shadow.getSourceLocation(), // Where is it being applied
					determineRelKind(munger).getName(), // What kind of advice?
					((Advice) munger).hasDynamicTests() // Is a runtime test being stuffed in the code?
					);
		}

		if (!getMessageHandler().isIgnoring(IMessage.WEAVEINFO)) {
			reportWeavingMessage(munger, shadow);
		}

		if (getModel() != null) {
			AsmRelationshipProvider.addAdvisedRelationship(getModelAsAsmManager(), shadow, munger);
		}
	}

	/*
	 * Report a message about the advice weave that has occurred. Some messing about to make it pretty ! This code is just asking
	 * for an NPE to occur ...
	 */
	private void reportWeavingMessage(ShadowMunger munger, Shadow shadow) {
		Advice advice = (Advice) munger;
		AdviceKind aKind = advice.getKind();
		// Only report on interesting advice kinds ...
		if (aKind == null || advice.getConcreteAspect() == null) {
			// We suspect someone is programmatically driving the weaver
			// (e.g. IdWeaveTestCase in the weaver testcases)
			return;
		}
		if (!(aKind.equals(AdviceKind.Before) || aKind.equals(AdviceKind.After) || aKind.equals(AdviceKind.AfterReturning)
				|| aKind.equals(AdviceKind.AfterThrowing) || aKind.equals(AdviceKind.Around) || aKind.equals(AdviceKind.Softener))) {
			return;
		}

		// synchronized blocks are implemented with multiple monitor_exit instructions in the bytecode
		// (one for normal exit from the method, one for abnormal exit), we only want to tell the user
		// once we have advised the end of the sync block, even though under the covers we will have
		// woven both exit points
		if (shadow.getKind() == Shadow.SynchronizationUnlock) {
			if (advice.lastReportedMonitorExitJoinpointLocation == null) {
				// this is the first time through, let's continue...
				advice.lastReportedMonitorExitJoinpointLocation = shadow.getSourceLocation();
			} else {
				if (areTheSame(shadow.getSourceLocation(), advice.lastReportedMonitorExitJoinpointLocation)) {
					// Don't report it again!
					advice.lastReportedMonitorExitJoinpointLocation = null;
					return;
				}
				// hmmm, this means some kind of nesting is going on, urgh
				advice.lastReportedMonitorExitJoinpointLocation = shadow.getSourceLocation();
			}
		}

		String description = advice.getKind().toString();
		String advisedType = shadow.getEnclosingType().getName();
		String advisingType = advice.getConcreteAspect().getName();
		Message msg = null;
		if (advice.getKind().equals(AdviceKind.Softener)) {
			msg = WeaveMessage.constructWeavingMessage(WeaveMessage.WEAVEMESSAGE_SOFTENS, new String[] { advisedType,
					beautifyLocation(shadow.getSourceLocation()), advisingType, beautifyLocation(munger.getSourceLocation()) },
					advisedType, advisingType);
		} else {
			boolean runtimeTest = advice.hasDynamicTests();
			String joinPointDescription = shadow.toString();
			msg = WeaveMessage
					.constructWeavingMessage(WeaveMessage.WEAVEMESSAGE_ADVISES,
							new String[] { joinPointDescription, advisedType, beautifyLocation(shadow.getSourceLocation()),
									description, advisingType, beautifyLocation(munger.getSourceLocation()),
									(runtimeTest ? " [with runtime test]" : "") }, advisedType, advisingType);
			// Boolean.toString(runtimeTest)});
		}
		getMessageHandler().handleMessage(msg);
	}

	private boolean areTheSame(ISourceLocation locA, ISourceLocation locB) {
		if (locA == null) {
			return locB == null;
		}
		if (locB == null) {
			return false;
		}
		if (locA.getLine() != locB.getLine()) {
			return false;
		}
		File fA = locA.getSourceFile();
		File fB = locA.getSourceFile();
		if (fA == null) {
			return fB == null;
		}
		if (fB == null) {
			return false;
		}
		return fA.getName().equals(fB.getName());
	}

	/*
	 * Ensure we report a nice source location - particular in the case where the source info is missing (binary weave).
	 */
	private String beautifyLocation(ISourceLocation isl) {
		StringBuffer nice = new StringBuffer();
		if (isl == null || isl.getSourceFile() == null || isl.getSourceFile().getName().indexOf("no debug info available") != -1) {
			nice.append("no debug info available");
		} else {
			// can't use File.getName() as this fails when a Linux box encounters a path created on Windows and vice-versa
			int takeFrom = isl.getSourceFile().getPath().lastIndexOf('/');
			if (takeFrom == -1) {
				takeFrom = isl.getSourceFile().getPath().lastIndexOf('\\');
			}
			int binary = isl.getSourceFile().getPath().lastIndexOf('!');
			if (binary != -1 && binary < takeFrom) {
				// we have been woven by a binary aspect
				String pathToBinaryLoc = isl.getSourceFile().getPath().substring(0, binary + 1);
				if (pathToBinaryLoc.indexOf(".jar") != -1) {
					// only want to add the extra info if we're from a jar file
					int lastSlash = pathToBinaryLoc.lastIndexOf('/');
					if (lastSlash == -1) {
						lastSlash = pathToBinaryLoc.lastIndexOf('\\');
					}
					nice.append(pathToBinaryLoc.substring(lastSlash + 1));
				}
			}
			nice.append(isl.getSourceFile().getPath().substring(takeFrom + 1));
			if (isl.getLine() != 0) {
				nice.append(":").append(isl.getLine());
			}
			// if it's a binary file then also want to give the file name
			if (isl.getSourceFileName() != null) {
				nice.append("(from " + isl.getSourceFileName() + ")");
			}
		}
		return nice.toString();
	}

	private static List<String> makeDefaultClasspath(String cp) {
		List<String> classPath = new ArrayList<String>();
		classPath.addAll(getPathEntries(cp));
		classPath.addAll(getPathEntries(ClassPath.getClassPath()));
		return classPath;

	}

	private static List<String> getPathEntries(String s) {
		List<String> ret = new ArrayList<String>();
		StringTokenizer tok = new StringTokenizer(s, File.pathSeparator);
		while (tok.hasMoreTokens()) {
			ret.add(tok.nextToken());
		}
		return ret;
	}

	public BcelWorld(List classPath, IMessageHandler handler, ICrossReferenceHandler xrefHandler) {
		// this.aspectPath = new ClassPathManager(aspectPath, handler);
		this.classPath = new ClassPathManager(classPath, handler);
		setMessageHandler(handler);
		setCrossReferenceHandler(xrefHandler);
		// Tell BCEL to use us for resolving any classes
		delegate = this;
	}

	public BcelWorld(ClassPathManager cpm, IMessageHandler handler, ICrossReferenceHandler xrefHandler) {
		classPath = cpm;
		setMessageHandler(handler);
		setCrossReferenceHandler(xrefHandler);
		// Tell BCEL to use us for resolving any classes
		delegate = this;
	}

	
Build a World from a ClassLoader, for LTW support
Params:
  • loader –
  • handler –
  • xrefHandler –
/** * Build a World from a ClassLoader, for LTW support * * @param loader * @param handler * @param xrefHandler */
public BcelWorld(ClassLoader loader, IMessageHandler handler, ICrossReferenceHandler xrefHandler) { classPath = null; loaderRef = new BcelWeakClassLoaderReference(loader); setMessageHandler(handler); setCrossReferenceHandler(xrefHandler); // Tell BCEL to use us for resolving any classes // delegate = getClassLoaderRepositoryFor(loader); } public void ensureRepositorySetup() { if (delegate == null) { delegate = getClassLoaderRepositoryFor(loaderRef); } } public Repository getClassLoaderRepositoryFor(ClassLoaderReference loader) { if (bcelRepositoryCaching) { return new ClassLoaderRepository(loader); } else { return new NonCachingClassLoaderRepository(loader); } } public void addPath(String name) { classPath.addPath(name, this.getMessageHandler()); } // ---- various interactions with bcel public static Type makeBcelType(UnresolvedType type) { return Type.getType(type.getErasureSignature()); } static Type[] makeBcelTypes(UnresolvedType[] types) { Type[] ret = new Type[types.length]; for (int i = 0, len = types.length; i < len; i++) { ret[i] = makeBcelType(types[i]); } return ret; } public static Type[] makeBcelTypes(String[] types) { if (types == null || types.length==0 ) { return null; } Type[] ret = new Type[types.length]; for (int i=0, len=types.length; i<len; i++) { ret[i] = makeBcelType(types[i]); } return ret; } public static Type makeBcelType(String type) { return Type.getType(type); } static String[] makeBcelTypesAsClassNames(UnresolvedType[] types) { String[] ret = new String[types.length]; for (int i = 0, len = types.length; i < len; i++) { ret[i] = types[i].getName(); } return ret; } public static UnresolvedType fromBcel(Type t) { return UnresolvedType.forSignature(t.getSignature()); } static UnresolvedType[] fromBcel(Type[] ts) { UnresolvedType[] ret = new UnresolvedType[ts.length]; for (int i = 0, len = ts.length; i < len; i++) { ret[i] = fromBcel(ts[i]); } return ret; } public ResolvedType resolve(Type t) { return resolve(fromBcel(t)); } @Override protected ReferenceTypeDelegate resolveDelegate(ReferenceType ty) { String name = ty.getName(); ensureAdvancedConfigurationProcessed(); JavaClass jc = lookupJavaClass(classPath, name); if (jc == null) { // Anyone else to ask? if (typeDelegateResolvers != null) { for (TypeDelegateResolver tdr : typeDelegateResolvers) { ReferenceTypeDelegate delegate = tdr.getDelegate(ty); if (delegate != null) { return delegate; } } } return null; } else { return buildBcelDelegate(ty, jc, false, false); } } public BcelObjectType buildBcelDelegate(ReferenceType type, JavaClass jc, boolean artificial, boolean exposedToWeaver) { BcelObjectType ret = new BcelObjectType(type, jc, artificial, exposedToWeaver); return ret; } private JavaClass lookupJavaClass(ClassPathManager classPath, String name) { if (classPath == null) { try { ensureRepositorySetup(); JavaClass jc = delegate.loadClass(name); if (trace.isTraceEnabled()) { trace.event("lookupJavaClass", this, new Object[] { name, jc }); } return jc; } catch (ClassNotFoundException e) { if (trace.isTraceEnabled()) { trace.error("Unable to find class '" + name + "' in repository", e); } return null; } } ClassPathManager.ClassFile file = null; try { file = classPath.find(UnresolvedType.forName(name)); if (file == null) { return null; } ClassParser parser = new ClassParser(file.getInputStream(), file.getPath()); JavaClass jc = parser.parse(); return jc; } catch (IOException ioe) { if (trace.isTraceEnabled()) { trace.error("IOException whilst processing class",ioe); } return null; } finally { if (file != null) { file.close(); } } } public BcelObjectType addSourceObjectType(JavaClass jc, boolean artificial) { return addSourceObjectType(jc.getClassName(), jc, artificial); } public BcelObjectType addSourceObjectType(String classname, JavaClass jc, boolean artificial) { BcelObjectType ret = null; if (!jc.getClassName().equals(classname)) { throw new RuntimeException(jc.getClassName() + "!=" + classname); } String signature = UnresolvedType.forName(jc.getClassName()).getSignature(); ResolvedType resolvedTypeFromTypeMap = typeMap.get(signature); if (resolvedTypeFromTypeMap != null && !(resolvedTypeFromTypeMap instanceof ReferenceType)) { // what on earth is it then? See pr 112243 StringBuffer exceptionText = new StringBuffer(); exceptionText.append("Found invalid (not a ReferenceType) entry in the type map. "); exceptionText.append("Signature=[" + signature + "] Found=[" + resolvedTypeFromTypeMap + "] Class=[" + resolvedTypeFromTypeMap.getClass() + "]"); throw new BCException(exceptionText.toString()); } ReferenceType referenceTypeFromTypeMap = (ReferenceType) resolvedTypeFromTypeMap; if (referenceTypeFromTypeMap == null) { if (jc.isGeneric() && isInJava5Mode()) { ReferenceType rawType = ReferenceType.fromTypeX(UnresolvedType.forRawTypeName(jc.getClassName()), this); ret = buildBcelDelegate(rawType, jc, artificial, true); ReferenceType genericRefType = new ReferenceType(UnresolvedType.forGenericTypeSignature(signature, ret.getDeclaredGenericSignature()), this); rawType.setDelegate(ret); genericRefType.setDelegate(ret); rawType.setGenericType(genericRefType); typeMap.put(signature, rawType); } else { referenceTypeFromTypeMap = new ReferenceType(signature, this); ret = buildBcelDelegate(referenceTypeFromTypeMap, jc, artificial, true); typeMap.put(signature, referenceTypeFromTypeMap); } } else { ret = buildBcelDelegate(referenceTypeFromTypeMap, jc, artificial, true); } return ret; } public BcelObjectType addSourceObjectType(String classname, byte[] bytes, boolean artificial) { BcelObjectType retval = null; String signature = UnresolvedType.forName(classname).getSignature(); ResolvedType resolvedTypeFromTypeMap = typeMap.get(signature); if (resolvedTypeFromTypeMap != null && !(resolvedTypeFromTypeMap instanceof ReferenceType)) { // what on earth is it then? See pr 112243 StringBuffer exceptionText = new StringBuffer(); exceptionText.append("Found invalid (not a ReferenceType) entry in the type map. "); exceptionText.append("Signature=[" + signature + "] Found=[" + resolvedTypeFromTypeMap + "] Class=[" + resolvedTypeFromTypeMap.getClass() + "]"); throw new BCException(exceptionText.toString()); } ReferenceType referenceTypeFromTypeMap = (ReferenceType) resolvedTypeFromTypeMap; if (referenceTypeFromTypeMap == null) { JavaClass jc = Utility.makeJavaClass(classname, bytes); if (jc.isGeneric() && isInJava5Mode()) { referenceTypeFromTypeMap = ReferenceType.fromTypeX(UnresolvedType.forRawTypeName(jc.getClassName()), this); retval = buildBcelDelegate(referenceTypeFromTypeMap, jc, artificial, true); ReferenceType genericRefType = new ReferenceType(UnresolvedType.forGenericTypeSignature(signature, retval.getDeclaredGenericSignature()), this); referenceTypeFromTypeMap.setDelegate(retval); genericRefType.setDelegate(retval); referenceTypeFromTypeMap.setGenericType(genericRefType); typeMap.put(signature, referenceTypeFromTypeMap); } else { referenceTypeFromTypeMap = new ReferenceType(signature, this); retval = buildBcelDelegate(referenceTypeFromTypeMap, jc, artificial, true); typeMap.put(signature, referenceTypeFromTypeMap); } } else { ReferenceTypeDelegate existingDelegate = referenceTypeFromTypeMap.getDelegate(); if (!(existingDelegate instanceof BcelObjectType)) { throw new IllegalStateException("For " + classname + " should be BcelObjectType, but is " + existingDelegate.getClass()); } retval = (BcelObjectType) existingDelegate; // Note1: If the type is already exposed to the weaver (retval.isExposedToWeaver()) then this is likely // to be a hotswap reweave so build a new delegate, don't accidentally use the old data. // Note2: Also seen when LTW and another agent precedes the AspectJ agent. Earlier in LTW // a type is resolved (and ends up in the typemap but not exposed to the weaver at that time) // then later LTW actually is attempted on this type. We end up here with different // bytes to the current delegate if the earlier agent has modified them. See PR488216 // if (retval.isArtificial() || retval.isExposedToWeaver()) { retval = buildBcelDelegate(referenceTypeFromTypeMap, Utility.makeJavaClass(classname, bytes), artificial, true); // } } return retval; } void deleteSourceObjectType(UnresolvedType ty) { typeMap.remove(ty.getSignature()); } public static Member makeFieldJoinPointSignature(LazyClassGen cg, FieldInstruction fi) { ConstantPool cpg = cg.getConstantPool(); return MemberImpl.field(fi.getClassName(cpg), (fi.opcode == Constants.GETSTATIC || fi.opcode == Constants.PUTSTATIC) ? Modifier.STATIC : 0, fi.getName(cpg), fi.getSignature(cpg)); } public Member makeJoinPointSignatureFromMethod(LazyMethodGen mg, MemberKind kind) { Member ret = mg.getMemberView(); if (ret == null) { int mods = mg.getAccessFlags(); if (mg.getEnclosingClass().isInterface()) { mods |= Modifier.INTERFACE; } return new ResolvedMemberImpl(kind, UnresolvedType.forName(mg.getClassName()), mods, fromBcel(mg.getReturnType()), mg.getName(), fromBcel(mg.getArgumentTypes())); } else { return ret; } } public Member makeJoinPointSignatureForMonitorEnter(LazyClassGen cg, InstructionHandle h) { return MemberImpl.monitorEnter(); } public Member makeJoinPointSignatureForMonitorExit(LazyClassGen cg, InstructionHandle h) { return MemberImpl.monitorExit(); } public Member makeJoinPointSignatureForArrayConstruction(LazyClassGen cg, InstructionHandle handle) { Instruction i = handle.getInstruction(); ConstantPool cpg = cg.getConstantPool(); Member retval = null; if (i.opcode == Constants.ANEWARRAY) { // ANEWARRAY arrayInstruction = (ANEWARRAY)i; Type ot = i.getType(cpg); UnresolvedType ut = fromBcel(ot); ut = UnresolvedType.makeArray(ut, 1); retval = MemberImpl.method(ut, Modifier.PUBLIC, UnresolvedType.VOID, "<init>", new ResolvedType[] { INT }); } else if (i instanceof MULTIANEWARRAY) { MULTIANEWARRAY arrayInstruction = (MULTIANEWARRAY) i; UnresolvedType ut = null; short dimensions = arrayInstruction.getDimensions(); ObjectType ot = arrayInstruction.getLoadClassType(cpg); if (ot != null) { ut = fromBcel(ot); ut = UnresolvedType.makeArray(ut, dimensions); } else { Type t = arrayInstruction.getType(cpg); ut = fromBcel(t); } ResolvedType[] parms = new ResolvedType[dimensions]; for (int ii = 0; ii < dimensions; ii++) { parms[ii] = INT; } retval = MemberImpl.method(ut, Modifier.PUBLIC, UnresolvedType.VOID, "<init>", parms); } else if (i.opcode == Constants.NEWARRAY) { // NEWARRAY arrayInstruction = (NEWARRAY)i; Type ot = i.getType(); UnresolvedType ut = fromBcel(ot); retval = MemberImpl.method(ut, Modifier.PUBLIC, UnresolvedType.VOID, "<init>", new ResolvedType[] { INT }); } else { throw new BCException("Cannot create array construction signature for this non-array instruction:" + i); } return retval; } public Member makeJoinPointSignatureForMethodInvocation(LazyClassGen cg, InvokeInstruction ii) { ConstantPool cpg = cg.getConstantPool(); String name = ii.getName(cpg); String declaring = ii.getClassName(cpg); UnresolvedType declaringType = null; String signature = ii.getSignature(cpg); // 307147 if (name.startsWith("ajc$privMethod$")) { // The invoke is on a privileged accessor. These may be created for different // kinds of target, not necessarily just private methods. In bug 307147 it is // for a private method. This code is identifying the particular case in 307147 try { declaringType = UnresolvedType.forName(declaring); String typeNameAsFoundInAccessorName = declaringType.getName().replace('.', '_'); int indexInAccessorName = name.lastIndexOf(typeNameAsFoundInAccessorName); if (indexInAccessorName != -1) { String methodName = name.substring(indexInAccessorName+typeNameAsFoundInAccessorName.length()+1); ResolvedType resolvedDeclaringType = declaringType.resolve(this); ResolvedMember[] methods = resolvedDeclaringType.getDeclaredMethods(); for (ResolvedMember method: methods) { if (method.getName().equals(methodName) && method.getSignature().equals(signature) && Modifier.isPrivate(method.getModifiers())) { return method; } } } } catch (Exception e) { // Remove this once confident above code isn't having unexpected side effects // Added 1.8.7 e.printStackTrace(); } } int modifier = (ii instanceof INVOKEINTERFACE) ? Modifier.INTERFACE : (ii.opcode == Constants.INVOKESTATIC) ? Modifier.STATIC : (ii.opcode == Constants.INVOKESPECIAL && !name .equals("<init>")) ? Modifier.PRIVATE : 0; // in Java 1.4 and after, static method call of super class within // subclass method appears // as declared by the subclass in the bytecode - but they are not // see #104212 if (ii.opcode == Constants.INVOKESTATIC) { ResolvedType appearsDeclaredBy = resolve(declaring); // look for the method there for (Iterator<ResolvedMember> iterator = appearsDeclaredBy.getMethods(true, true); iterator.hasNext();) { ResolvedMember method = iterator.next(); if (Modifier.isStatic(method.getModifiers())) { if (name.equals(method.getName()) && signature.equals(method.getSignature())) { // we found it declaringType = method.getDeclaringType(); break; } } } } if (declaringType == null) { if (declaring.charAt(0) == '[') { declaringType = UnresolvedType.forSignature(declaring); } else { declaringType = UnresolvedType.forName(declaring); } } return MemberImpl.method(declaringType, modifier, name, signature); } @Override public String toString() { StringBuffer buf = new StringBuffer(); buf.append("BcelWorld("); // buf.append(shadowMungerMap); buf.append(")"); return buf.toString(); }
Retrieve a bcel delegate for an aspect - this will return NULL if the delegate is an EclipseSourceType and not a BcelObjectType - this happens quite often when incrementally compiling.
/** * Retrieve a bcel delegate for an aspect - this will return NULL if the delegate is an EclipseSourceType and not a * BcelObjectType - this happens quite often when incrementally compiling. */
public static BcelObjectType getBcelObjectType(ResolvedType concreteAspect) { if (concreteAspect == null) { return null; } if (!(concreteAspect instanceof ReferenceType)) { // Might be Missing return null; } ReferenceTypeDelegate rtDelegate = ((ReferenceType) concreteAspect).getDelegate(); if (rtDelegate instanceof BcelObjectType) { return (BcelObjectType) rtDelegate; } else { return null; } } public void tidyUp() { // At end of compile, close any open files so deletion of those archives // is possible classPath.closeArchives(); typeMap.report(); typeMap.demote(true); // ResolvedType.resetPrimitives(); } // / The repository interface methods @Override public JavaClass findClass(String className) { return lookupJavaClass(classPath, className); } @Override public JavaClass loadClass(String className) throws ClassNotFoundException { return lookupJavaClass(classPath, className); } @Override public void storeClass(JavaClass clazz) { // doesn't need to do anything } @Override public void removeClass(JavaClass clazz) { throw new RuntimeException("Not implemented"); } @Override public JavaClass loadClass(Class clazz) throws ClassNotFoundException { throw new RuntimeException("Not implemented"); } @Override public void clear() { delegate.clear(); // throw new RuntimeException("Not implemented"); }
The aim of this method is to make sure a particular type is 'ok'. Some operations on the delegate for a type modify it and this method is intended to undo that... see pr85132
/** * The aim of this method is to make sure a particular type is 'ok'. Some operations on the delegate for a type modify it and * this method is intended to undo that... see pr85132 */
@Override public void validateType(UnresolvedType type) { ResolvedType result = typeMap.get(type.getSignature()); if (result == null) { return; // We haven't heard of it yet } if (!result.isExposedToWeaver()) { return; // cant need resetting } result.ensureConsistent(); // If we want to rebuild it 'from scratch' then: // ClassParser cp = new ClassParser(new // ByteArrayInputStream(newbytes),new String(cs)); // try { // rt.setDelegate(makeBcelObjectType(rt,cp.parse(),true)); // } catch (ClassFormatException e) { // e.printStackTrace(); // } catch (IOException e) { // e.printStackTrace(); // } }
Apply a single declare parents - return true if we change the type
/** * Apply a single declare parents - return true if we change the type */
private boolean applyDeclareParents(DeclareParents p, ResolvedType onType) { boolean didSomething = false; List<ResolvedType> newParents = p.findMatchingNewParents(onType, true); if (!newParents.isEmpty()) { didSomething = true; BcelObjectType classType = BcelWorld.getBcelObjectType(onType); // System.err.println("need to do declare parents for: " + onType); for (ResolvedType newParent : newParents) { // We set it here so that the imminent matching for ITDs can // succeed - we still haven't done the necessary changes to the class file // itself (like transform super calls) - that is done in // BcelTypeMunger.mungeNewParent() // classType.addParent(newParent); onType.addParent(newParent); ResolvedTypeMunger newParentMunger = new NewParentTypeMunger(newParent, p.getDeclaringType()); newParentMunger.setSourceLocation(p.getSourceLocation()); onType.addInterTypeMunger(new BcelTypeMunger(newParentMunger, getCrosscuttingMembersSet() .findAspectDeclaringParents(p)), false); } } return didSomething; }
Apply a declare @type - return true if we change the type
/** * Apply a declare @type - return true if we change the type */
private boolean applyDeclareAtType(DeclareAnnotation decA, ResolvedType onType, boolean reportProblems) { boolean didSomething = false; if (decA.matches(onType)) { if (onType.hasAnnotation(decA.getAnnotation().getType())) { // already has it return false; } AnnotationAJ annoX = decA.getAnnotation(); // check the annotation is suitable for the target boolean isOK = checkTargetOK(decA, onType, annoX); if (isOK) { didSomething = true; ResolvedTypeMunger newAnnotationTM = new AnnotationOnTypeMunger(annoX); newAnnotationTM.setSourceLocation(decA.getSourceLocation()); onType.addInterTypeMunger(new BcelTypeMunger(newAnnotationTM, decA.getAspect().resolve(this)), false); decA.copyAnnotationTo(onType); } } return didSomething; }
Apply the specified declare @field construct to any matching fields in the specified type.
Params:
  • deca – the declare annotation targeting fields
  • type – the type to check for members matching the declare annotation
Returns:true if something matched and the type was modified
/** * Apply the specified declare @field construct to any matching fields in the specified type. * @param deca the declare annotation targeting fields * @param type the type to check for members matching the declare annotation * @return true if something matched and the type was modified */
private boolean applyDeclareAtField(DeclareAnnotation deca, ResolvedType type) { boolean changedType = false; ResolvedMember[] fields = type.getDeclaredFields(); for (ResolvedMember field: fields) { if (deca.matches(field, this)) { AnnotationAJ anno = deca.getAnnotation(); if (!field.hasAnnotation(anno.getType())) { field.addAnnotation(anno); changedType=true; } } } return changedType; }
Checks for an @target() on the annotation and if found ensures it allows the annotation to be attached to the target type that matched.
/** * Checks for an @target() on the annotation and if found ensures it allows the annotation to be attached to the target type * that matched. */
private boolean checkTargetOK(DeclareAnnotation decA, ResolvedType onType, AnnotationAJ annoX) { if (annoX.specifiesTarget()) { if ((onType.isAnnotation() && !annoX.allowedOnAnnotationType()) || (!annoX.allowedOnRegularType())) { return false; } } return true; } // Hmmm - very similar to the code in BcelWeaver.weaveParentTypeMungers - // this code // doesn't need to produce errors/warnings though as it won't really be // weaving. protected void weaveInterTypeDeclarations(ResolvedType onType) { List<DeclareParents> declareParentsList = getCrosscuttingMembersSet().getDeclareParents(); if (onType.isRawType()) { onType = onType.getGenericType(); } onType.clearInterTypeMungers(); List<DeclareParents> decpToRepeat = new ArrayList<DeclareParents>(); boolean aParentChangeOccurred = false; boolean anAnnotationChangeOccurred = false; // First pass - apply all decp mungers for (Iterator<DeclareParents> i = declareParentsList.iterator(); i.hasNext();) { DeclareParents decp = i.next(); boolean typeChanged = applyDeclareParents(decp, onType); if (typeChanged) { aParentChangeOccurred = true; } else { // Perhaps it would have matched if a 'dec @type' had // modified the type if (!decp.getChild().isStarAnnotation()) { decpToRepeat.add(decp); } } } // Still first pass - apply all dec @type mungers for (DeclareAnnotation decA : getCrosscuttingMembersSet().getDeclareAnnotationOnTypes()) { boolean typeChanged = applyDeclareAtType(decA, onType, true); if (typeChanged) { anAnnotationChangeOccurred = true; } } // apply declare @field for (DeclareAnnotation deca: getCrosscuttingMembersSet().getDeclareAnnotationOnFields()) { if (applyDeclareAtField(deca,onType)) { anAnnotationChangeOccurred = true; } } while ((aParentChangeOccurred || anAnnotationChangeOccurred) && !decpToRepeat.isEmpty()) { anAnnotationChangeOccurred = aParentChangeOccurred = false; List<DeclareParents> decpToRepeatNextTime = new ArrayList<DeclareParents>(); for (DeclareParents decp: decpToRepeat) { if (applyDeclareParents(decp, onType)) { aParentChangeOccurred = true; } else { decpToRepeatNextTime.add(decp); } } for (DeclareAnnotation deca: getCrosscuttingMembersSet().getDeclareAnnotationOnTypes()) { if (applyDeclareAtType(deca, onType, false)) { anAnnotationChangeOccurred = true; } } for (DeclareAnnotation deca: getCrosscuttingMembersSet().getDeclareAnnotationOnFields()) { if (applyDeclareAtField(deca, onType)) { anAnnotationChangeOccurred = true; } } decpToRepeat = decpToRepeatNextTime; } } @Override public IWeavingSupport getWeavingSupport() { return bcelWeavingSupport; } @Override public void reportCheckerMatch(Checker checker, Shadow shadow) { IMessage iMessage = new Message(checker.getMessage(shadow), shadow.toString(), checker.isError() ? IMessage.ERROR : IMessage.WARNING, shadow.getSourceLocation(), null, new ISourceLocation[] { checker.getSourceLocation() }, true, 0, -1, -1); getMessageHandler().handleMessage(iMessage); if (getCrossReferenceHandler() != null) { getCrossReferenceHandler() .addCrossReference( checker.getSourceLocation(), shadow.getSourceLocation(), (checker.isError() ? IRelationship.Kind.DECLARE_ERROR.getName() : IRelationship.Kind.DECLARE_WARNING .getName()), false); } if (getModel() != null) { AsmRelationshipProvider.addDeclareErrorOrWarningRelationship(getModelAsAsmManager(), shadow, checker); } } public AsmManager getModelAsAsmManager() { return (AsmManager) getModel(); // For now... always an AsmManager in a bcel environment } void raiseError(String message) { getMessageHandler().handleMessage(MessageUtil.error(message)); }
These are aop.xml files that can be used to alter the aspects that actually apply from those passed in - and also their scope of application to other files in the system.
Params:
  • xmlFiles – list of File objects representing any aop.xml files passed in to configure the build process
/** * These are aop.xml files that can be used to alter the aspects that actually apply from those passed in - and also their scope * of application to other files in the system. * * @param xmlFiles list of File objects representing any aop.xml files passed in to configure the build process */
public void setXmlFiles(List<File> xmlFiles) { if (!isXmlConfiguredWorld && !xmlFiles.isEmpty()) { raiseError("xml configuration files only supported by the compiler when -xmlConfigured option specified"); return; } if (!xmlFiles.isEmpty()) { xmlConfiguration = new WeavingXmlConfig(this, WeavingXmlConfig.MODE_COMPILE); } for (File xmlfile : xmlFiles) { try { Definition d = DocumentParser.parse(xmlfile.toURI().toURL()); xmlConfiguration.add(d); } catch (MalformedURLException e) { raiseError("Unexpected problem processing XML config file '" + xmlfile.getName() + "' :" + e.getMessage()); } catch (Exception e) { raiseError("Unexpected problem processing XML config file '" + xmlfile.getName() + "' :" + e.getMessage()); } } }
Add a scoped aspects where the scoping was defined in an aop.xml file and this world is being used in a LTW configuration
/** * Add a scoped aspects where the scoping was defined in an aop.xml file and this world is being used in a LTW configuration */
public void addScopedAspect(String name, String scope) { this.isXmlConfiguredWorld = true; if (xmlConfiguration == null) { xmlConfiguration = new WeavingXmlConfig(this, WeavingXmlConfig.MODE_LTW); } xmlConfiguration.addScopedAspect(name, scope); } public void setXmlConfigured(boolean b) { this.isXmlConfiguredWorld = b; } @Override public boolean isXmlConfigured() { return isXmlConfiguredWorld && xmlConfiguration != null; } public WeavingXmlConfig getXmlConfiguration() { return xmlConfiguration; } @Override public boolean isAspectIncluded(ResolvedType aspectType) { if (!isXmlConfigured()) { return true; } return xmlConfiguration.specifiesInclusionOfAspect(aspectType.getName()); } @Override public TypePattern getAspectScope(ResolvedType declaringType) { return xmlConfiguration.getScopeFor(declaringType.getName()); } @Override public boolean hasUnsatisfiedDependency(ResolvedType aspectType) { String aspectName = aspectType.getName(); if (aspectType.hasAnnotations()) { AnnotationAJ[] annos = aspectType.getAnnotations(); for (AnnotationAJ anno: annos) { if (anno.getTypeName().equals("org.aspectj.lang.annotation.RequiredTypes")) { String values = anno.getStringFormOfValue("value"); // Example: "[A,org.foo.Bar]" if (values != null && values.length() > 2) { values = values.substring(1,values.length()-1); StringTokenizer tokenizer = new StringTokenizer(values,","); boolean anythingMissing = false; while (tokenizer.hasMoreElements()) { String requiredTypeName = tokenizer.nextToken(); ResolvedType rt = resolve(UnresolvedType.forName(requiredTypeName)); if (rt.isMissing()) { if (!getMessageHandler().isIgnoring(IMessage.INFO)) { getMessageHandler().handleMessage( MessageUtil.info("deactivating aspect '" + aspectName + "' as it requires type '" + requiredTypeName + "' which cannot be found on the classpath")); } anythingMissing = true; if (aspectRequiredTypes == null) { aspectRequiredTypes = new HashMap<String,String>(); } // Record that it has an invalid type reference aspectRequiredTypes.put(aspectName,requiredTypeName); } } if (anythingMissing) { return true; } else { return false; } } else { // no value specified for annotation return false; } } } } if (aspectRequiredTypes == null) { // no aspects require anything, so there can be no unsatisfied dependencies return false; } if (!aspectRequiredTypesProcessed.contains(aspectName)) { String requiredTypeName = aspectRequiredTypes.get(aspectName); if (requiredTypeName==null) { aspectRequiredTypesProcessed.add(aspectName); return false; } else { ResolvedType rt = resolve(UnresolvedType.forName(requiredTypeName)); if (!rt.isMissing()) { aspectRequiredTypesProcessed.add(aspectName); aspectRequiredTypes.remove(aspectName); return false; } else { if (!getMessageHandler().isIgnoring(IMessage.INFO)) { getMessageHandler().handleMessage( MessageUtil.info("deactivating aspect '" + aspectName + "' as it requires type '" + requiredTypeName + "' which cannot be found on the classpath")); } aspectRequiredTypesProcessed.add(aspectName); return true; } } } return aspectRequiredTypes.containsKey(aspectName); } private List<String> aspectRequiredTypesProcessed = new ArrayList<String>(); private Map<String, String> aspectRequiredTypes = null; public void addAspectRequires(String aspectClassName, String requiredType) { if (aspectRequiredTypes == null) { aspectRequiredTypes = new HashMap<String, String>(); } aspectRequiredTypes.put(aspectClassName,requiredType); }
A WeavingXmlConfig is initially a collection of definitions from XML files - once the world is ready and weaving is running it will initialize and transform those definitions into an optimized set of values (eg. resolve type patterns and string names to real entities). It can then answer questions quickly: (1) is this aspect included in the weaving? (2) Is there a scope specified for this aspect and does it include type X?
/** * A WeavingXmlConfig is initially a collection of definitions from XML files - once the world is ready and weaving is running * it will initialize and transform those definitions into an optimized set of values (eg. resolve type patterns and string * names to real entities). It can then answer questions quickly: (1) is this aspect included in the weaving? (2) Is there a * scope specified for this aspect and does it include type X? * */
static class WeavingXmlConfig { final static int MODE_COMPILE = 1; final static int MODE_LTW = 2; private int mode; private boolean initialized = false; // Lazily done private List<Definition> definitions = new ArrayList<Definition>(); private List<String> resolvedIncludedAspects = new ArrayList<String>(); private Map<String, TypePattern> scopes = new HashMap<String, TypePattern>(); // these are not set for LTW mode (exclusion of these fast match patterns is handled before the weaver/world are used) private List<String> includedFastMatchPatterns = Collections.emptyList(); private List<TypePattern> includedPatterns = Collections.emptyList(); private List<String> excludedFastMatchPatterns = Collections.emptyList(); private List<TypePattern> excludedPatterns = Collections.emptyList(); private BcelWorld world; public WeavingXmlConfig(BcelWorld bcelWorld, int mode) { this.world = bcelWorld; this.mode = mode; } public void add(Definition d) { definitions.add(d); } public void addScopedAspect(String aspectName, String scope) { ensureInitialized(); resolvedIncludedAspects.add(aspectName); try { TypePattern scopePattern = new PatternParser(scope).parseTypePattern(); scopePattern.resolve(world); scopes.put(aspectName, scopePattern); if (!world.getMessageHandler().isIgnoring(IMessage.INFO)) { world.getMessageHandler().handleMessage( MessageUtil.info("Aspect '" + aspectName + "' is scoped to apply against types matching pattern '" + scopePattern.toString() + "'")); } } catch (Exception e) { world.getMessageHandler().handleMessage( MessageUtil.error("Unable to parse scope as type pattern. Scope was '" + scope + "': " + e.getMessage())); } } public void ensureInitialized() { if (!initialized) { try { resolvedIncludedAspects = new ArrayList<String>(); // Process the definitions into something more optimal for (Definition definition : definitions) { List<String> aspectNames = definition.getAspectClassNames(); for (String name : aspectNames) { resolvedIncludedAspects.add(name); // TODO check for existence? // ResolvedType resolvedAspect = resolve(UnresolvedType.forName(name)); // if (resolvedAspect.isMissing()) { // // ERROR // } else { // resolvedIncludedAspects.add(resolvedAspect); // } String scope = definition.getScopeForAspect(name); if (scope != null) { // Resolve the type pattern try { TypePattern scopePattern = new PatternParser(scope).parseTypePattern(); scopePattern.resolve(world); scopes.put(name, scopePattern); if (!world.getMessageHandler().isIgnoring(IMessage.INFO)) { world.getMessageHandler().handleMessage( MessageUtil.info("Aspect '" + name + "' is scoped to apply against types matching pattern '" + scopePattern.toString() + "'")); } } catch (Exception e) { // TODO definitions should remember which file they came from, for inclusion in this message world.getMessageHandler().handleMessage( MessageUtil.error("Unable to parse scope as type pattern. Scope was '" + scope + "': " + e.getMessage())); } } } try { List<String> includePatterns = definition.getIncludePatterns(); if (includePatterns.size() > 0) { includedPatterns = new ArrayList<TypePattern>(); includedFastMatchPatterns = new ArrayList<String>(); } for (String includePattern : includePatterns) { if (includePattern.endsWith("..*")) { // from 'blah.blah.blah..*' leave the 'blah.blah.blah.' includedFastMatchPatterns.add(includePattern.substring(0, includePattern.length() - 2)); } else { TypePattern includedPattern = new PatternParser(includePattern).parseTypePattern(); includedPatterns.add(includedPattern); } } List<String> excludePatterns = definition.getExcludePatterns(); if (excludePatterns.size() > 0) { excludedPatterns = new ArrayList<TypePattern>(); excludedFastMatchPatterns = new ArrayList<String>(); } for (String excludePattern : excludePatterns) { if (excludePattern.endsWith("..*")) { // from 'blah.blah.blah..*' leave the 'blah.blah.blah.' excludedFastMatchPatterns.add(excludePattern.substring(0, excludePattern.length() - 2)); } else { TypePattern excludedPattern = new PatternParser(excludePattern).parseTypePattern(); excludedPatterns.add(excludedPattern); } } } catch (ParserException pe) { // TODO definitions should remember which file they came from, for inclusion in this message world.getMessageHandler().handleMessage( MessageUtil.error("Unable to parse type pattern: " + pe.getMessage())); } } } finally { initialized = true; } } } public boolean specifiesInclusionOfAspect(String name) { ensureInitialized(); return resolvedIncludedAspects.contains(name); } public TypePattern getScopeFor(String name) { return scopes.get(name); } // Can't quite follow the same rules for exclusion as used for loadtime weaving: // "The set of types to be woven are those types matched by at least one weaver include element and not matched by any // weaver // exclude element. If there are no weaver include statements then all non-excluded types are included." // Since if the weaver is seeing it during this kind of build, the type is implicitly included. So all we should check // for is exclusion public boolean excludesType(ResolvedType type) { if (mode == MODE_LTW) { return false; } String typename = type.getName(); boolean excluded = false; for (String excludedPattern : excludedFastMatchPatterns) { if (typename.startsWith(excludedPattern)) { excluded = true; break; } } if (!excluded) { for (TypePattern excludedPattern : excludedPatterns) { if (excludedPattern.matchesStatically(type)) { excluded = true; break; } } } return excluded; } } @Override public TypeMap getTypeMap() { return typeMap; } @Override public boolean isLoadtimeWeaving() { return false; } public void addTypeDelegateResolver(TypeDelegateResolver typeDelegateResolver) { if (typeDelegateResolvers == null) { typeDelegateResolvers = new ArrayList<TypeDelegateResolver>(); } typeDelegateResolvers.add(typeDelegateResolver); } @Override public void classWriteEvent(char[][] compoundName) { typeMap.classWriteEvent(new String(CharOperation.concatWith(compoundName, '.'))); }
Force demote a type.
/** * Force demote a type. */
public void demote(ResolvedType type) { typeMap.demote(type); } }