001: /*
002: * @(#)ModifiedTargeters.java
003: *
004: * Copyright (C) 2004 Matt Albrecht
005: * groboclown@users.sourceforge.net
006: * http://groboutils.sourceforge.net
007: *
008: * Permission is hereby granted, free of charge, to any person obtaining a
009: * copy of this software and associated documentation files (the "Software"),
010: * to deal in the Software without restriction, including without limitation
011: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
012: * and/or sell copies of the Software, and to permit persons to whom the
013: * Software is furnished to do so, subject to the following conditions:
014: *
015: * The above copyright notice and this permission notice shall be included in
016: * all copies or substantial portions of the Software.
017: *
018: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
019: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
020: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
021: * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
022: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
023: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
024: * DEALINGS IN THE SOFTWARE.
025: */
026:
027: package net.sourceforge.groboutils.codecoverage.v2.compiler;
028:
029: import java.util.HashMap;
030: import java.util.HashSet;
031: import java.util.Iterator;
032: import java.util.LinkedList;
033: import java.util.List;
034: import java.util.Map;
035: import java.util.Set;
036:
037: import org.apache.bcel.generic.BranchInstruction;
038: import org.apache.bcel.generic.CodeExceptionGen;
039: import org.apache.bcel.generic.InstructionHandle;
040: import org.apache.bcel.generic.InstructionTargeter;
041: import org.apache.bcel.generic.LineNumberGen;
042: import org.apache.bcel.generic.LocalVariableGen;
043: import org.apache.bcel.generic.MethodGen;
044: import org.apache.bcel.generic.ObjectType;
045:
046: /**
047: * Keeps track of different target types, and updates them according to
048: * the changes in the instructions.
049: * <p>
050: * This class exists due to a bug in the CodeException mod code in BCEL.
051: *
052: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
053: * @version $Date: 2004/04/15 05:48:25 $
054: * @since Jan 21, 2004, 2002
055: */
056: class ModifiedTargeters {
057: private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
058: .getLogger(ModifiedTargeters.class);
059: private MethodGen methGen;
060: private Map startToCodeException = new HashMap();
061: /* no longer needed
062: private Map endToCodeException = new HashMap();
063: */
064: private Map handlerToCodeException = new HashMap();
065: private List myCegs = new LinkedList();
066: private InstructionHandle lastInstruction;
067:
068: private static class MyCEG {
069: private InstructionHandle origStartPC;
070: private InstructionHandle newStartPC;
071:
072: private InstructionHandle origEndPC;
073: private InstructionHandle newEndPC;
074:
075: private InstructionHandle origHandlerPC;
076: private InstructionHandle newHandlerPC;
077:
078: private ObjectType type;
079:
080: public MyCEG(InstructionHandle s, InstructionHandle e,
081: InstructionHandle h, ObjectType t) {
082: this .type = t;
083: this .origStartPC = s;
084: this .origEndPC = e;
085: this .origHandlerPC = h;
086:
087: if (t == null) {
088: LOG
089: .debug(">> Found null object type in CodeExceptionGen");
090: }
091: }
092:
093: public CodeExceptionGen addCodeException(MethodGen mg) {
094: InstructionHandle sp = this .newStartPC;
095: InstructionHandle ep = this .newEndPC;
096: InstructionHandle hp = this .newHandlerPC;
097:
098: if (sp == null) {
099: sp = this .origStartPC;
100: }
101: if (ep == null) {
102: ep = this .origEndPC;
103: }
104: if (hp == null) {
105: hp = this .origHandlerPC;
106: }
107: LOG.debug("Changing exception handler for type "
108: + this .type + ": moved from (start = "
109: + this .origStartPC + ", end = " + this .origEndPC
110: + ", handler = " + this .origHandlerPC
111: + ") to (start = " + sp + ", end = " + ep
112: + ", handler = " + hp + ")");
113: return mg.addExceptionHandler(sp, ep, hp, this .type);
114: }
115:
116: public void setStartPC(InstructionHandle h) {
117: if (this .newStartPC == null) {
118: LOG.debug("Updating internal start PC for exception "
119: + this .type + " to point to " + h);
120: this .newStartPC = h;
121: }
122: }
123:
124: /* no longer supported
125: public void setEndPC( InstructionHandle h )
126: {
127: LOG.debug( "Updating internal end PC for exception "+
128: this.type+" to point to "+h );
129: this.newEndPC = h;
130: }
131: */
132:
133: public void setHandlerPC(InstructionHandle h) {
134: if (this .newHandlerPC == null) {
135: LOG.debug("Updating internal handler PC for exception "
136: + this .type + " to point to " + h);
137: this .newHandlerPC = h;
138: }
139: }
140: }
141:
142: /**
143: */
144: ModifiedTargeters(MethodGen mg) {
145: if (mg == null) {
146: throw new IllegalArgumentException("no null args");
147: }
148: this .methGen = mg;
149:
150: // setup the maps...
151: CodeExceptionGen ceg[] = mg.getExceptionHandlers();
152: for (int i = 0; i < ceg.length; ++i) {
153: MyCEG ceg2 = new MyCEG(ceg[i].getStartPC(), ceg[i]
154: .getEndPC(), ceg[i].getHandlerPC(), ceg[i]
155: .getCatchType());
156: this .myCegs.add(ceg2);
157: putList(this .startToCodeException, ceg2.origStartPC, ceg2);
158: /* no longer supported
159: putList( this.endToCodeException, ceg2.origEndPC, ceg2 );
160: */
161: putList(this .handlerToCodeException, ceg2.origHandlerPC,
162: ceg2);
163: }
164:
165: // discover the last instruction in the method
166: Iterator list = mg.getInstructionList().iterator();
167: while (list.hasNext()) {
168: this .lastInstruction = (InstructionHandle) list.next();
169: }
170: }
171:
172: /**
173: * Inserted a probe into the list. "Inserting" means it goes before the
174: * instruction that's being probed, so the "end" parts of targeters don't
175: * need to be updated...
176: */
177: public void insertProbe(InstructionHandle instr,
178: InstructionHandle probe) {
179: // the original point that was being updated may not be what was
180: // last referenced for updating handlers.
181: // HOWEVER, the way probes are added is by always inserting them
182: // before the code that needs to be probed. That means, adding
183: // probe A will redirect targeters to A, then adding probe B
184: // will put B after A but before the instructions, so the targeters
185: // MUST NOT be updated, otherwise probe A will not be targeted
186: // correctly.
187:
188: InstructionTargeter[] its = instr.getTargeters();
189: if (its != null) {
190: for (int j = 0; j < its.length; j++) {
191: LOG.debug("Found for instruction " + instr
192: + " targeter " + its[j]);
193:
194: // don't call updateTargeters, as that is faulty, and doesn't
195: // match what's really going on. Also, that technique was
196: // wrong when dealing with multiple probes added to the same
197: // instruction, multiple times.
198:
199: if (its[j] instanceof LineNumberGen) {
200: LOG.debug("Updating line number to point to probe "
201: + probe);
202: ((LineNumberGen) its[j]).setInstruction(probe);
203: } else if (its[j] instanceof LocalVariableGen) {
204: // this definitely needs to be separated out, so that
205: // if start and end are the same instruction, the end
206: // doesn't point to the new probe only.
207: if (instr.equals(((LocalVariableGen) its[j])
208: .getStart())) {
209: LOG
210: .debug("Updating local variable start to point to probe "
211: + probe);
212: ((LocalVariableGen) its[j]).setStart(probe);
213: }
214: } else if (its[j] instanceof CodeExceptionGen) {
215: // ignore - we'll handle this separately
216: LOG.debug("Ignoring CodeExceptionGen");
217: } else if (its[j] instanceof BranchInstruction) {
218: // here we need to perform an update, so that the
219: // "Select" table can correctly be updated.
220: LOG
221: .debug("Updating branch instruction to point to probe "
222: + probe);
223: ((BranchInstruction) its[j]).updateTarget(instr,
224: probe);
225: } else {
226: throw new IllegalStateException(
227: "Unexpected targeter type ("
228: + its[j].getClass().getName()
229: + ") for instruction " + instr);
230: }
231: }
232: }
233:
234: // if the inserted probe points to the start of a code exception,
235: // then redirect the code exception to the probe.
236: Iterator iter = getList(this .startToCodeException, instr);
237: while (iter.hasNext()) {
238: MyCEG mceg = (MyCEG) iter.next();
239: mceg.setStartPC(probe);
240: }
241:
242: // if the inserted probe points to the start of an exception handler,
243: // then redirect the code exception.
244: iter = getList(this .handlerToCodeException, instr);
245: while (iter.hasNext()) {
246: MyCEG mceg = (MyCEG) iter.next();
247: mceg.setHandlerPC(probe);
248: }
249: }
250:
251: /*
252: * Here, we only need to update the end probes. Note that the last
253: * instruction in the method will change with these added probes.
254: *
255: * WARNING!!!! This should probably be pulled out, as it's very dangerous
256: * to add a probe at the end of a method - it will occur after
257: * any returns!
258: public void appendProbe( InstructionHandle probe )
259: {
260: // get new last instruction
261: InstructionHandle lastProbe = probe;
262: while (lastProbe.getNext() != null)
263: {
264: lastProbe = lastProbe.getNext();
265: }
266:
267:
268: InstructionTargeter [] its = this.lastInstruction.getTargeters();
269: if (its != null)
270: {
271: for (int j = 0; j < its.length; j++)
272: {
273: // don't call updateTargeters, as that is faulty, and doesn't
274: // match what's really going on. Also, that technique was
275: // wrong when dealing with multiple probes added to the same
276: // instruction, multiple times.
277:
278: if (its[j] instanceof LineNumberGen)
279: {
280: // don't do anything
281: }
282: else
283: if (its[j] instanceof LocalVariableGen)
284: {
285: // this definitely needs to be separated out, so that
286: // if start and end are the same instruction, the start
287: // doesn't point to the new probe only.
288: if (this.lastInstruction.equals(
289: ((LocalVariableGen)its[j]).getEnd() ))
290: {
291: ((LocalVariableGen)its[j]).setEnd( lastProbe );
292: }
293: }
294: else
295: if (its[j] instanceof CodeExceptionGen)
296: {
297: // ignore - we'll handle this separately
298: }
299: else
300: if (its[j] instanceof BranchInstruction)
301: {
302: // don't do anything
303: }
304: else
305: {
306: throw new IllegalStateException(
307: "Unexpected targeter type (" +
308: its[j].getClass().getName() +
309: ") for instruction " + this.lastInstruction );
310: }
311: }
312: }
313:
314: // if the inserted probe points to the start of a code exception,
315: // then redirect the code exception to the probe.
316: Iterator iter = getList( this.endToCodeException, this.lastInstruction );
317: while (iter.hasNext())
318: {
319: MyCEG mceg = (MyCEG)iter.next();
320: mceg.setEndPC( lastProbe );
321: }
322:
323: // update the last instruction pointer
324: this.lastInstruction = lastProbe;
325: }
326: */
327:
328: /**
329: * Updates the targeters after a series of insert and appends.
330: * Note that the targeters might be updated during the inserts and
331: * appends.
332: */
333: public void update() {
334: this .methGen.removeExceptionHandlers();
335: Iterator iter = this .myCegs.iterator();
336: while (iter.hasNext()) {
337: ((MyCEG) iter.next()).addCodeException(this .methGen);
338: }
339: }
340:
341: private void putList(Map map, Object key, Object value) {
342: Set s = (Set) map.get(key);
343: if (s == null) {
344: s = new HashSet();
345: map.put(key, s);
346: }
347: s.add(value);
348: }
349:
350: /**
351: * Never returns null.
352: */
353: private Iterator getList(Map map, Object key) {
354: Set s = (Set) map.get(key);
355: if (s == null) {
356: s = new HashSet();
357: }
358: return s.iterator();
359: }
360: }
|