aboutsummaryrefslogtreecommitdiffstats
path: root/org.aspectj.matcher/src/main/java/org/aspectj/weaver/patterns/Pointcut.java
blob: a7fa5c3cf8847175e0f758623cd4c40b024daa80 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
/* *******************************************************************
 * 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.patterns;

import java.io.IOException;
import java.util.Map;

import org.aspectj.util.FuzzyBoolean;
import org.aspectj.util.TypeSafeEnum;
import org.aspectj.weaver.Advice;
import org.aspectj.weaver.AdviceKind;
import org.aspectj.weaver.BCException;
import org.aspectj.weaver.Checker;
import org.aspectj.weaver.CompressingDataOutputStream;
import org.aspectj.weaver.ISourceContext;
import org.aspectj.weaver.IntMap;
import org.aspectj.weaver.PoliceExtensionUse;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.Shadow;
import org.aspectj.weaver.ShadowMunger;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.VersionedDataInputStream;
import org.aspectj.weaver.World;
import org.aspectj.weaver.ast.Literal;
import org.aspectj.weaver.ast.Test;

/**
 * The lifecycle of Pointcuts is modeled by Pointcut.State. It has three things:
 *
 * <p>
 * Creation -- SYMBOLIC -- then resolve(IScope) -- RESOLVED -- concretize(...) -- CONCRETE
 *
 * @author Erik Hilsdale
 * @author Jim Hugunin
 *
 *         A day in the life of a pointcut.... - AMC. ==========================================
 *
 *         Pointcuts are created by the PatternParser, which is called by ajdt to parse a pointcut from the PseudoTokens AST node
 *         (which in turn are part of a PointcutDesignator AST node).
 *
 *         Pointcuts are resolved by ajdt when an AdviceDeclaration or a PointcutDeclaration has its statements resolved. This
 *         happens as part of completeTypeBindings in the AjLookupEnvironment which is called after the diet parse phase of the
 *         compiler. Named pointcuts, and references to named pointcuts are instances of ReferencePointcut.
 *
 *         At the end of the compilation process, the pointcuts are serialized (write method) into attributes in the class file.
 *
 *         When the weaver loads the class files, it unpacks the attributes and deserializes the pointcuts (read). All aspects are
 *         added to the world, by calling addOrReplaceAspect on the crosscutting members set of the world. When aspects are added or
 *         replaced, the crosscutting members in the aspect are extracted as ShadowMungers (each holding a pointcut). The
 *         ShadowMungers are concretized, which concretizes the pointcuts. At this stage ReferencePointcuts are replaced by their
 *         declared content.
 *
 *         During weaving, the weaver processes type by type. It first culls potentially matching ShadowMungers by calling the
 *         fastMatch method on their pointcuts. Only those that might match make it through to the next phase. At the next phase,
 *         all of the shadows within the type are created and passed to the pointcut for matching (match).
 *
 *         When the actual munging happens, matched pointcuts are asked for their residue (findResidue) - the runtime test if any.
 *         Because of negation, findResidue may be called on pointcuts that could never match the shadow.
 *
 */
public abstract class Pointcut extends PatternNode {
	public static final class State extends TypeSafeEnum {
		public State(String name, int key) {
			super(name, key);
		}
	}

	/**
	 * ATAJ the name of the formal for which we don't want any warning when unbound since we consider them as implicitly bound. f.e.
	 * JoinPoint for @AJ advices
	 */
	public String[] m_ignoreUnboundBindingForNames = EMPTY_STRING_ARRAY;

	public static final String[] EMPTY_STRING_ARRAY = new String[0];

	public static final State SYMBOLIC = new State("symbolic", 0);
	public static final State RESOLVED = new State("resolved", 1);
	public static final State CONCRETE = new State("concrete", 2);

	protected byte pointcutKind;

	public State state;

	protected int lastMatchedShadowId;
	private FuzzyBoolean lastMatchedShadowResult;
	private String[] typeVariablesInScope = EMPTY_STRING_ARRAY;

	protected boolean hasBeenParameterized = false;

	/**
	 * Constructor for Pattern.
	 */
	public Pointcut() {
		super();
		this.state = SYMBOLIC;
	}

	/**
	 * Could I match any shadows in the code defined within this type?
	 */
	public abstract FuzzyBoolean fastMatch(FastMatchInfo info);

	/**
	 * The set of ShadowKinds that this Pointcut could possibly match - an int whose bits are set according to the Kinds specified
	 * in Shadow.java
	 */
	public abstract int couldMatchKinds();

	public String[] getTypeVariablesInScope() {
		return typeVariablesInScope;
	}

	public void setTypeVariablesInScope(String[] typeVars) {
		this.typeVariablesInScope = typeVars;
	}

	/**
	 * Do I really match this shadow? XXX implementors need to handle state
	 */
	public final FuzzyBoolean match(Shadow shadow) {
		if (shadow.shadowId == lastMatchedShadowId) {
			return lastMatchedShadowResult;
		}
		FuzzyBoolean ret;
		// this next test will prevent a lot of un-needed matching going on....
		if (shadow.getKind().isSet(couldMatchKinds())) {
			ret = matchInternal(shadow);
		} else {
			ret = FuzzyBoolean.NO;
		}
		lastMatchedShadowId = shadow.shadowId;
		lastMatchedShadowResult = ret;
		return ret;
	}

	protected abstract FuzzyBoolean matchInternal(Shadow shadow);

	public static final byte KINDED = 1;
	public static final byte WITHIN = 2;
	public static final byte THIS_OR_TARGET = 3;
	public static final byte ARGS = 4;
	public static final byte AND = 5;
	public static final byte OR = 6;
	public static final byte NOT = 7;
	public static final byte REFERENCE = 8;
	public static final byte IF = 9;
	public static final byte CFLOW = 10;
	public static final byte WITHINCODE = 12;
	public static final byte HANDLER = 13;
	public static final byte IF_TRUE = 14;
	public static final byte IF_FALSE = 15;
	public static final byte ANNOTATION = 16;
	public static final byte ATWITHIN = 17;
	public static final byte ATWITHINCODE = 18;
	public static final byte ATTHIS_OR_TARGET = 19;

	public static final byte NONE = 20; // DO NOT CHANGE OR REORDER THIS SEQUENCE, THIS VALUE CAN BE PUT OUT BY ASPECTJ1.2.1

	public static final byte ATARGS = 21;
	public static final byte USER_EXTENSION = 22;

	public byte getPointcutKind() {
		return pointcutKind;
	}

	// internal, only called from resolve
	protected abstract void resolveBindings(IScope scope, Bindings bindings);

	/**
	 * Returns this pointcut mutated
	 */
	public final Pointcut resolve(IScope scope) {
		assertState(SYMBOLIC);
		Bindings bindingTable = new Bindings(scope.getFormalCount());
		IScope bindingResolutionScope = scope;
		if (typeVariablesInScope.length > 0) {
			bindingResolutionScope = new ScopeWithTypeVariables(typeVariablesInScope, scope);
		}
		this.resolveBindings(bindingResolutionScope, bindingTable);
		bindingTable.checkAllBound(bindingResolutionScope);
		this.state = RESOLVED;
		return this;
	}

	/**
	 * Returns a new pointcut Only used by test cases
	 */
	public final Pointcut concretize(ResolvedType inAspect, ResolvedType declaringType, int arity) {
		Pointcut ret = concretize(inAspect, declaringType, IntMap.idMap(arity));
		// copy the unbound ignore list
		ret.m_ignoreUnboundBindingForNames = m_ignoreUnboundBindingForNames;
		return ret;
	}

	// XXX this is the signature we're moving to
	public final Pointcut concretize(ResolvedType inAspect, ResolvedType declaringType, int arity, ShadowMunger advice) {
		// if (state == CONCRETE) return this; //???
		IntMap map = IntMap.idMap(arity);
		map.setEnclosingAdvice(advice);
		map.setConcreteAspect(inAspect);
		return concretize(inAspect, declaringType, map);
	}

	public boolean isDeclare(ShadowMunger munger) {
		if (munger == null) {
			return false; // ??? Is it actually an error if we get a null munger into this method.
		}
		if (munger instanceof Checker) {
			return true;
		}
		if (((Advice) munger).getKind().equals(AdviceKind.Softener)) {
			return true;
		}
		return false;
	}

	public final Pointcut concretize(ResolvedType inAspect, ResolvedType declaringType, IntMap bindings) {
		// !!! add this test -- assertState(RESOLVED);
		Pointcut ret = this.concretize1(inAspect, declaringType, bindings);
		if (shouldCopyLocationForConcretize()) {
			ret.copyLocationFrom(this);
		}
		ret.state = CONCRETE;
		// copy the unbound ignore list
		ret.m_ignoreUnboundBindingForNames = m_ignoreUnboundBindingForNames;
		return ret;
	}

	protected boolean shouldCopyLocationForConcretize() {
		return true;
	}

	/**
	 * Resolves and removes ReferencePointcuts, replacing with basic ones
	 *
	 * @param inAspect the aspect to resolve relative to
	 * @param bindings a Map from formal index in the current lexical context &rarr; formal index in the concrete advice that will run
	 *
	 *        This must always return a new Pointcut object (even if the concretized Pointcut is identical to the resolved one).
	 *        That behavior is assumed in many places. XXX fix implementors to handle state
	 */
	protected abstract Pointcut concretize1(ResolvedType inAspect, ResolvedType declaringType, IntMap bindings);

	// XXX implementors need to handle state
	/**
	 * This can be called from NotPointcut even for Pointcuts that don't match the shadow
	 */
	public final Test findResidue(Shadow shadow, ExposedState state) {
		// if (shadow.shadowId == lastMatchedShadowId) return lastMatchedShadowResidue;
		Test ret = findResidueInternal(shadow, state);
		// lastMatchedShadowResidue = ret;
		lastMatchedShadowId = shadow.shadowId;
		return ret;
	}

	protected abstract Test findResidueInternal(Shadow shadow, ExposedState state);

	// XXX we're not sure whether or not this is needed
	// XXX currently it's unused we're keeping it around as a stub
	public void postRead(ResolvedType enclosingType) {
	}

	public static Pointcut read(VersionedDataInputStream s, ISourceContext context) throws IOException {
		byte kind = s.readByte();
		Pointcut ret;

		switch (kind) {
		case KINDED:
			ret = KindedPointcut.read(s, context);
			break;
		case WITHIN:
			ret = WithinPointcut.read(s, context);
			break;
		case THIS_OR_TARGET:
			ret = ThisOrTargetPointcut.read(s, context);
			break;
		case ARGS:
			ret = ArgsPointcut.read(s, context);
			break;
		case AND:
			ret = AndPointcut.read(s, context);
			break;
		case OR:
			ret = OrPointcut.read(s, context);
			break;
		case NOT:
			ret = NotPointcut.read(s, context);
			break;
		case REFERENCE:
			ret = ReferencePointcut.read(s, context);
			break;
		case IF:
			ret = IfPointcut.read(s, context);
			break;
		case CFLOW:
			ret = CflowPointcut.read(s, context);
			break;
		case WITHINCODE:
			ret = WithincodePointcut.read(s, context);
			break;
		case HANDLER:
			ret = HandlerPointcut.read(s, context);
			break;
		case IF_TRUE:
			ret = IfPointcut.makeIfTruePointcut(RESOLVED);
			break;
		case IF_FALSE:
			ret = IfPointcut.makeIfFalsePointcut(RESOLVED);
			break;
		case ANNOTATION:
			ret = AnnotationPointcut.read(s, context);
			break;
		case ATWITHIN:
			ret = WithinAnnotationPointcut.read(s, context);
			break;
		case ATWITHINCODE:
			ret = WithinCodeAnnotationPointcut.read(s, context);
			break;
		case ATTHIS_OR_TARGET:
			ret = ThisOrTargetAnnotationPointcut.read(s, context);
			break;
		case ATARGS:
			ret = ArgsAnnotationPointcut.read(s, context);
			break;
		case NONE:
			ret = makeMatchesNothing(RESOLVED);
			break;
		default:
			throw new BCException("unknown kind: " + kind);
		}
		ret.state = RESOLVED;
		ret.pointcutKind = kind;
		return ret;

	}

	public void check(ISourceContext ctx, World world) {
		// this is a quick visitor...
		PoliceExtensionUse pointcutPolice = new PoliceExtensionUse(world, this);
		this.accept(pointcutPolice, null);
		if (pointcutPolice.synchronizationDesignatorEncountered()) {
			world.setSynchronizationPointcutsInUse();
		}
	}

	// public void prepare(Shadow shadow) {}

	// ---- test method

	public static Pointcut fromString(String str) {
		PatternParser parser = new PatternParser(str);
		return parser.parsePointcut();
	}

	static class MatchesNothingPointcut extends Pointcut {
		@Override
		protected Test findResidueInternal(Shadow shadow, ExposedState state) {
			return Literal.FALSE; // can only get here if an earlier error occurred
		}

		@Override
		public int couldMatchKinds() {
			return Shadow.NO_SHADOW_KINDS_BITS;
		}

		@Override
		public FuzzyBoolean fastMatch(FastMatchInfo type) {
			return FuzzyBoolean.NO;
		}

		@Override
		protected FuzzyBoolean matchInternal(Shadow shadow) {
			return FuzzyBoolean.NO;
		}

		@Override
		public void resolveBindings(IScope scope, Bindings bindings) {
		}

		@Override
		public void postRead(ResolvedType enclosingType) {
		}

		@Override
		public Pointcut concretize1(ResolvedType inAspect, ResolvedType declaringType, IntMap bindings) {
			return makeMatchesNothing(state);
		}

		@Override
		public void write(CompressingDataOutputStream s) throws IOException {
			s.writeByte(NONE);
		}

		@Override
		public String toString() {
			return "";
		}

		@Override
		public Object accept(PatternNodeVisitor visitor, Object data) {
			return visitor.visit(this, data);
		}

		@Override
		public Pointcut parameterizeWith(Map<String, UnresolvedType> typeVariableMap, World w) {
			return this;
		}
	}

	// public static Pointcut MatchesNothing = new MatchesNothingPointcut();
	// ??? there could possibly be some good optimizations to be done at this point
	public static Pointcut makeMatchesNothing(State state) {
		Pointcut ret = new MatchesNothingPointcut();
		ret.state = state;
		return ret;
	}

	public void assertState(State state) {
		if (this.state != state) {
			throw new BCException("expected state: " + state + " got: " + this.state);
		}
	}

	public abstract Pointcut parameterizeWith(Map<String, UnresolvedType> typeVariableMap, World w);

}