View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.javaflow.bytecode.transformation.bcel.analyser;
18  
19  import org.apache.bcel.generic.ASTORE;
20  import org.apache.bcel.generic.ATHROW;
21  import org.apache.bcel.generic.BranchInstruction;
22  import org.apache.bcel.generic.CodeExceptionGen;
23  import org.apache.bcel.generic.GotoInstruction;
24  import org.apache.bcel.generic.IndexedInstruction;
25  import org.apache.bcel.generic.Instruction;
26  import org.apache.bcel.generic.InstructionHandle;
27  import org.apache.bcel.generic.JsrInstruction;
28  import org.apache.bcel.generic.LocalVariableInstruction;
29  import org.apache.bcel.generic.MethodGen;
30  import org.apache.bcel.generic.RET;
31  import org.apache.bcel.generic.ReturnInstruction;
32  import org.apache.bcel.generic.Select;
33  import org.apache.bcel.verifier.exc.AssertionViolatedException;
34  import org.apache.bcel.verifier.exc.StructuralCodeConstraintException;
35  
36  import java.util.ArrayList;
37  import java.util.HashSet;
38  import java.util.Hashtable;
39  import java.util.Iterator;
40  import java.util.Set;
41  
42  /**
43   * Instances of this class contain information about the subroutines
44   * found in a code array of a method.
45   * This implementation considers the top-level (the instructions
46   * reachable without a JSR or JSR_W starting off from the first
47   * instruction in a code array of a method) being a special subroutine;
48   * see getTopLevel() for that.
49   * Please note that the definition of subroutines in the Java Virtual
50   * Machine Specification, Second Edition is somewhat incomplete.
51   * Therefore, JustIce uses an own, more rigid notion.
52   * Basically, a subroutine is a piece of code that starts at the target
53   * of a JSR of JSR_W instruction and ends at a corresponding RET
54   * instruction. Note also that the control flow of a subroutine
55   * may be complex and non-linear; and that subroutines may be nested.
56   *
57   * WARNING! These classes are a fork of the bcel verifier.
58   *
59   * @version $Id: Subroutines.java 480487 2006-11-29 08:54:42Z bayard $
60   * @author <A HREF="http://www.inf.fu-berlin.de/~ehaase"/>Enver Haase</A>
61   * @see #getTopLevel()
62   */
63  public class Subroutines{
64  	/**
65  	 * This inner class implements the Subroutine interface.
66  	 */
67  	private class SubroutineImpl implements Subroutine{
68  		/**
69  		 * UNSET, a symbol for an uninitialized localVariable
70  		 * field. This is used for the "top-level" Subroutine;
71  		 * i.e. no subroutine.
72  		 */
73  		private final int UNSET = -1;
74  
75  		/**
76  		 * The Local Variable slot where the first
77  		 * instruction of this subroutine (an ASTORE) stores
78  		 * the JsrInstruction's ReturnAddress in and
79  		 * the RET of this subroutine operates on.
80  		 */
81  		private int localVariable = UNSET;
82  
83  		/** The instructions that belong to this subroutine. */
84  		private Set instructions;
85  
86  		/*
87  		 * Refer to the Subroutine interface for documentation.
88  		 */
89  		public boolean contains(InstructionHandle inst){
90  			return instructions.contains(inst);
91  		}
92  
93  		/**
94  		 * The JSR or JSR_W instructions that define this
95  		 * subroutine by targeting it.
96  		 */
97  		private HashSet theJSRs = new HashSet();
98  
99  		/**
100 		 * The RET instruction that leaves this subroutine.
101 		 */
102 		private InstructionHandle theRET;
103 
104 		/**
105 		 * Returns a String representation of this object, merely
106 		 * for debugging purposes.
107 		 * (Internal) Warning: Verbosity on a problematic subroutine may cause
108 		 * stack overflow errors due to recursive subSubs() calls.
109 		 * Don't use this, then.
110 		 */
111 		public String toString(){
112 			String ret = "Subroutine: Local variable is '"+localVariable+"', JSRs are '"+theJSRs+"', RET is '"+theRET+"', Instructions: '"+instructions.toString()+"'.";
113 
114 			ret += " Accessed local variable slots: '";
115 			int[] alv = getAccessedLocalsIndices();
116 			for (int i=0; i<alv.length; i++){
117 				ret += alv[i]+" ";
118 			}
119 			ret+="'.";
120 
121 			ret += " Recursively (via subsub...routines) accessed local variable slots: '";
122 			alv = getRecursivelyAccessedLocalsIndices();
123 			for (int i=0; i<alv.length; i++){
124 				ret += alv[i]+" ";
125 			}
126 			ret+="'.";
127 
128 			return ret;
129 		}
130 
131 		/**
132 		 * Sets the leaving RET instruction. Must be invoked after all instructions are added.
133 		 * Must not be invoked for top-level 'subroutine'.
134 		 */
135 		void setLeavingRET(){
136 			if (localVariable == UNSET){
137 				throw new AssertionViolatedException("setLeavingRET() called for top-level 'subroutine' or forgot to set local variable first.");
138 			}
139 			Iterator iter = instructions.iterator();
140 			InstructionHandle ret = null;
141 			while(iter.hasNext()){
142 				InstructionHandle actual = (InstructionHandle) iter.next();
143 				if (actual.getInstruction() instanceof RET){
144 					if (ret != null){
145 						throw new StructuralCodeConstraintException("Subroutine with more then one RET detected: '"+ret+"' and '"+actual+"'.");
146 					}
147 					else{
148 						ret = actual;
149 					}
150 				}
151 			}
152 			if (ret == null){
153 				throw new StructuralCodeConstraintException("Subroutine without a RET detected.");
154 			}
155 			if (((RET) ret.getInstruction()).getIndex() != localVariable){
156 				throw new StructuralCodeConstraintException("Subroutine uses '"+ret+"' which does not match the correct local variable '"+localVariable+"'.");
157 			}
158 			theRET = ret;
159 		}
160 
161 		/*
162 		 * Refer to the Subroutine interface for documentation.
163 		 */
164 		public InstructionHandle[] getEnteringJsrInstructions(){
165 			if (this == TOPLEVEL) {
166 				throw new AssertionViolatedException("getLeavingRET() called on top level pseudo-subroutine.");
167 			}
168 			InstructionHandle[] jsrs = new InstructionHandle[theJSRs.size()];
169 			return (InstructionHandle[]) (theJSRs.toArray(jsrs));
170 		}
171 
172 		/**
173 		 * Adds a new JSR or JSR_W that has this subroutine as its target.
174 		 */
175 		public void addEnteringJsrInstruction(InstructionHandle jsrInst){
176 			if ( (jsrInst == null) || (! (jsrInst.getInstruction() instanceof JsrInstruction))){
177 				throw new AssertionViolatedException("Expecting JsrInstruction InstructionHandle.");
178 			}
179 			if (localVariable == UNSET){
180 				throw new AssertionViolatedException("Set the localVariable first!");
181 			}
182 			else{
183 				// Something is wrong when an ASTORE is targeted that does not operate on the same local variable than the rest of the
184 				// JsrInstruction-targets and the RET.
185 				// (We don't know out leader here so we cannot check if we're really targeted!)
186 				if (localVariable != ((ASTORE) (((JsrInstruction) jsrInst.getInstruction()).getTarget().getInstruction())).getIndex()){
187 					throw new AssertionViolatedException("Setting a wrong JsrInstruction.");
188 				}
189 			}
190 			theJSRs.add(jsrInst);
191 		}
192 
193 		/*
194 		 * Refer to the Subroutine interface for documentation.
195 		 */
196 		public InstructionHandle getLeavingRET(){
197 			if (this == TOPLEVEL) {
198 				throw new AssertionViolatedException("getLeavingRET() called on top level pseudo-subroutine.");
199 			}
200 			return theRET;
201 		}
202 
203 		/*
204 		 * Refer to the Subroutine interface for documentation.
205 		 */
206 		public InstructionHandle[] getInstructions(){
207 			InstructionHandle[] ret = new InstructionHandle[instructions.size()];
208 			return (InstructionHandle[]) instructions.toArray(ret);
209 		}
210 
211 		/* Satisfies Subroutine.getRecursivelyAccessedLocalsIndices(). */
212 		public int[] getRecursivelyAccessedLocalsIndices(){
213 			HashSet s = new HashSet();
214 			int[] lvs = getAccessedLocalsIndices();
215 			for (int j=0; j<lvs.length; j++){
216 				s.add(new Integer(lvs[j]));
217 			}
218 			_getRecursivelyAccessedLocalsIndicesHelper(s, this.subSubs());
219 			int[] ret = new int[s.size()];
220 			Iterator i = s.iterator();
221 			int j=-1;
222 			while (i.hasNext()){
223 				j++;
224 				ret[j] = ((Integer) i.next()).intValue();
225 			}
226 			return ret;
227 		}
228 
229 		/**
230 		 * A recursive helper method for getRecursivelyAccessedLocalsIndices().
231 		 * @see #getRecursivelyAccessedLocalsIndices()
232 		 */
233 		private void _getRecursivelyAccessedLocalsIndicesHelper(HashSet s, Subroutine[] subs){
234 			for (int i=0; i<subs.length; i++){
235 				int[] lvs = subs[i].getAccessedLocalsIndices();
236 				for (int j=0; j<lvs.length; j++){
237 					s.add(new Integer(lvs[j]));
238 				}
239 				if(subs[i].subSubs().length != 0){
240 					_getRecursivelyAccessedLocalsIndicesHelper(s, subs[i].subSubs());
241 				}
242 			}
243 		}
244 
245 		/*
246 		 * Satisfies Subroutine.getAccessedLocalIndices().
247 		 */
248 		public int[] getAccessedLocalsIndices(){
249 			//TODO: Implement caching.
250 			HashSet acc = new HashSet();
251 			if (theRET == null && this != TOPLEVEL){
252 				throw new AssertionViolatedException("This subroutine object must be built up completely before calculating accessed locals.");
253 			}
254 			Iterator i = instructions.iterator();
255 			while (i.hasNext()){
256 				InstructionHandle ih = (InstructionHandle) i.next();
257 				// RET is not a LocalVariableInstruction in the current version of BCEL.
258 				if (ih.getInstruction() instanceof LocalVariableInstruction || ih.getInstruction() instanceof RET){
259 					int idx = ((IndexedInstruction) (ih.getInstruction())).getIndex();
260 					acc.add(new Integer(idx));
261 					// LONG? DOUBLE?.
262 					try{
263 						// LocalVariableInstruction instances are typed without the need to look into
264 						// the constant pool.
265 						if (ih.getInstruction() instanceof LocalVariableInstruction){
266 							int s = ((LocalVariableInstruction) ih.getInstruction()).getType(null).getSize();
267 							if (s==2) acc.add(new Integer(idx+1));
268 						}
269 					}
270 					catch(RuntimeException re){
271 						throw new AssertionViolatedException("Oops. BCEL did not like NULL as a ConstantPoolGen object.");
272 					}
273 				}
274 			}
275 
276 			int[] ret = new int[acc.size()];
277 			i = acc.iterator();
278 			int j=-1;
279 			while (i.hasNext()){
280 				j++;
281 				ret[j] = ((Integer) i.next()).intValue();
282 			}
283 			return ret;
284 		}
285 
286 		/*
287 		 * Satisfies Subroutine.subSubs().
288 		 */
289 		public Subroutine[] subSubs(){
290 			HashSet h = new HashSet();
291 
292 			Iterator i = instructions.iterator();
293 			while (i.hasNext()){
294 				Instruction inst = ((InstructionHandle) i.next()).getInstruction();
295 				if (inst instanceof JsrInstruction){
296 					InstructionHandle targ = ((JsrInstruction) inst).getTarget();
297 					h.add(getSubroutine(targ));
298 				}
299 			}
300 			Subroutine[] ret = new Subroutine[h.size()];
301 			return (Subroutine[]) h.toArray(ret);
302 		}
303 
304 		/*
305 		 * Sets the local variable slot the ASTORE that is targeted
306 		 * by the JsrInstructions of this subroutine operates on.
307 		 * This subroutine's RET operates on that same local variable
308 		 * slot, of course.
309 		 */
310 		void setLocalVariable(int i){
311 			if (localVariable != UNSET){
312 				throw new AssertionViolatedException("localVariable set twice.");
313 			}
314 			else{
315 				localVariable = i;
316 			}
317 		}
318 
319 		/**
320 		 * The default constructor.
321 		 */
322 		public SubroutineImpl(){
323 		}
324 
325         void setInstructions(Set _instructions) {
326             this.instructions = _instructions;
327         }
328     }// end Inner Class SubrouteImpl
329 
330 	/**
331 	 * The Hashtable containing the subroutines found.
332 	 * Key: InstructionHandle of the leader of the subroutine.
333 	 * Elements: SubroutineImpl objects.
334 	 */
335 	private Hashtable subroutines = new Hashtable();
336 
337 	/**
338 	 * This is referring to a special subroutine, namely the
339 	 * top level. This is not really a subroutine but we use
340 	 * it to distinguish between top level instructions and
341 	 * unreachable instructions.
342 	 */
343 	public final Subroutine TOPLEVEL;
344 
345 	/**
346 	 * Constructor.
347 	 * @param mg A MethodGen object representing method to
348 	 * create the Subroutine objects of.
349 	 */
350 	public Subroutines(MethodGen mg){
351 
352 		InstructionHandle[] all = mg.getInstructionList().getInstructionHandles();
353 		CodeExceptionGen[] handlers = mg.getExceptionHandlers();
354 
355 		// Define our "Toplevel" fake subroutine.
356 		TOPLEVEL = new SubroutineImpl();
357 
358 		// Calculate "real" subroutines.
359 		HashSet sub_leaders = new HashSet(); // Elements: InstructionHandle
360 		for (int i=0; i<all.length; i++){
361 			Instruction inst = all[i].getInstruction();
362 			if (inst instanceof JsrInstruction){
363 				sub_leaders.add(((JsrInstruction) inst).getTarget());
364 			}
365 		}
366 
367 		// Build up the database.
368 		Iterator iter = sub_leaders.iterator();
369 		while (iter.hasNext()){
370 			SubroutineImpl sr = new SubroutineImpl();
371 			InstructionHandle astore = (InstructionHandle) (iter.next());
372 			sr.setLocalVariable( ((ASTORE) (astore.getInstruction())).getIndex() );
373 			subroutines.put(astore, sr);
374 		}
375 
376 		// Fake it a bit. We want a virtual "TopLevel" subroutine.
377 		subroutines.put(all[0], TOPLEVEL);
378 		sub_leaders.add(all[0]);
379 
380 		// Tell the subroutines about their JsrInstructions.
381 		// Note that there cannot be a JSR targeting the top-level
382 		// since "Jsr 0" is disallowed in Pass 3a.
383 		// Instructions shared by a subroutine and the toplevel are
384 		// disallowed and checked below, after the BFS.
385 		for (int i=0; i<all.length; i++){
386 			Instruction inst = all[i].getInstruction();
387 			if (inst instanceof JsrInstruction){
388 				InstructionHandle leader = ((JsrInstruction) inst).getTarget();
389 				((SubroutineImpl) getSubroutine(leader)).addEnteringJsrInstruction(all[i]);
390 			}
391 		}
392 
393 		// Now do a closure computation from every subroutine leader to find all the
394 		// instructions that belong to a subroutine.
395 		HashSet instructions_assigned = new HashSet(); // we don't want to assign an instruction to two or more Subroutine objects.
396 
397         iter = sub_leaders.iterator();
398 		while (iter.hasNext()){
399             // set of InstructionHandles reachable from the top
400             Set closure = new HashSet();
401 
402             // Init Queue. Start with the entry point of closure.
403             InstructionHandle leader = (InstructionHandle) (iter.next());
404 			ArrayList Q = new ArrayList();
405 			Q.add(leader);
406 
407             while (!Q.isEmpty()){
408                 while (!Q.isEmpty()){
409                     InstructionHandle u = (InstructionHandle) Q.remove(Q.size()-1);
410                     if(closure.add(u)) {
411                         InstructionHandle[] successors = getSuccessors(u);
412                         for (int i=0; i<successors.length; i++){
413                             Q.add(successors[i]);
414                         }
415                     }
416                 }
417                 // check if exception handlers are reachable from the closure computed thus far.
418                 // conceptually, an exception handler is a successor of each instruction inside
419                 // the protected region, but the computation works faster if we don't implement it
420                 // in a straight-forward way. Instead, we occasionally check if the first instruction
421                 // of the protected region is in the closure, and if so, we know that the exception
422                 // handler should also be in the closure.
423                 //
424                 // "one instruction must be always in at-most one subroutine" JVM requirement
425                 // guarantees that the entire protected region always belongs to the same closure,
426                 // so just checking the start instruction is suffice.
427                 for( int i=0; i<handlers.length; i++ ) {
428                     if(closure.contains(handlers[i].getStartPC())) {
429                         InstructionHandle handlerPC = handlers[i].getHandlerPC();
430                         if(!closure.contains(handlerPC)) {
431                             Q.add(handlerPC);
432                         }
433                     }
434                 }
435             }
436             // DFS ended above.
437             ((SubroutineImpl) (leader==all[0]?getTopLevel():getSubroutine(leader))).setInstructions(closure);
438 
439             for (Iterator itr = closure.iterator(); itr.hasNext();) {
440                 InstructionHandle h = (InstructionHandle) itr.next();
441                 if(!instructions_assigned.add(h)) {
442                     throw new StructuralCodeConstraintException("Instruction '"+h+"' is part of more than one subroutine (or of the top level and a subroutine).");
443                 }
444             }
445 
446             if (leader != all[0]){// If we don't deal with the top-level 'subroutine'
447 				((SubroutineImpl) getSubroutine(leader)).setLeavingRET();
448 			}
449 		}
450 
451 		// Now make sure no subroutine is calling a subroutine
452 		// that uses the same local variable for the RET as themselves
453 		// (recursively).
454 		// This includes that subroutines may not call themselves
455 		// recursively, even not through intermediate calls to other
456 		// subroutines.
457 		noRecursiveCalls(getTopLevel(), new HashSet());
458 
459 	}
460 
461 	/**
462 	 * This (recursive) utility method makes sure that
463 	 * no subroutine is calling a subroutine
464 	 * that uses the same local variable for the RET as themselves
465 	 * (recursively).
466 	 * This includes that subroutines may not call themselves
467 	 * recursively, even not through intermediate calls to other
468 	 * subroutines.
469 	 *
470 	 * @throws StructuralCodeConstraintException if the above constraint is not satisfied.
471 	 */
472 	private void noRecursiveCalls(Subroutine sub, HashSet set){
473 		Subroutine[] subs = sub.subSubs();
474 
475 		for (int i=0; i<subs.length; i++){
476 			int index = ((RET) (subs[i].getLeavingRET().getInstruction())).getIndex();
477 
478 			if (!set.add(new Integer(index))){
479 				// Don't use toString() here because of possibly infinite recursive subSubs() calls then.
480 				SubroutineImpl si = (SubroutineImpl) subs[i];
481 				throw new StructuralCodeConstraintException("Subroutine with local variable '"+si.localVariable+"', JSRs '"+si.theJSRs+"', RET '"+si.theRET+"' is called by a subroutine which uses the same local variable index as itself; maybe even a recursive call? JustIce's clean definition of a subroutine forbids both.");
482 			}
483 
484 			noRecursiveCalls(subs[i], set);
485 
486 			set.remove(new Integer(index));
487 		}
488 	}
489 
490 	/**
491 	 * Returns the Subroutine object associated with the given
492 	 * leader (that is, the first instruction of the subroutine).
493 	 * You must not use this to get the top-level instructions
494 	 * modeled as a Subroutine object.
495 	 *
496 	 * @see #getTopLevel()
497 	 */
498 	public Subroutine getSubroutine(InstructionHandle leader){
499 		Subroutine ret = (Subroutine) subroutines.get(leader);
500 
501 		if (ret == null){
502 			throw new AssertionViolatedException("Subroutine requested for an InstructionHandle that is not a leader of a subroutine.");
503 		}
504 
505 		if (ret == TOPLEVEL){
506 			throw new AssertionViolatedException("TOPLEVEL special subroutine requested; use getTopLevel().");
507 		}
508 
509 		return ret;
510 	}
511 
512 	/**
513 	 * Returns the subroutine object associated with the
514 	 * given instruction. This is a costly operation, you
515 	 * should consider using getSubroutine(InstructionHandle).
516 	 * Returns 'null' if the given InstructionHandle lies
517 	 * in so-called 'dead code', i.e. code that can never
518 	 * be executed.
519 	 *
520 	 * @see #getSubroutine(InstructionHandle)
521 	 * @see #getTopLevel()
522 	 */
523 	public Subroutine subroutineOf(InstructionHandle any){
524 		Iterator i = subroutines.values().iterator();
525 		while (i.hasNext()){
526 			Subroutine s = (Subroutine) i.next();
527 			if (s.contains(any)) return s;
528 		}
529 System.err.println("DEBUG: Please verify '"+any+"' lies in dead code.");
530 		return null;
531 		//throw new AssertionViolatedException("No subroutine for InstructionHandle found (DEAD CODE?).");
532 	}
533 
534 	/**
535 	 * For easy handling, the piece of code that is <B>not</B> a
536 	 * subroutine, the top-level, is also modeled as a Subroutine
537 	 * object.
538 	 * It is a special Subroutine object where <B>you must not invoke
539 	 * getEnteringJsrInstructions() or getLeavingRET()</B>.
540 	 *
541 	 * @see Subroutine#getEnteringJsrInstructions()
542 	 * @see Subroutine#getLeavingRET()
543 	 */
544 	public Subroutine getTopLevel(){
545 		return TOPLEVEL;
546 	}
547 
548     static final InstructionHandle[] empty = new InstructionHandle[0];
549 
550     /**
551 	 * A utility method that calculates the successors of a given InstructionHandle
552 	 * <B>in the same subroutine</B>. That means, a RET does not have any successors
553 	 * as defined here. A JsrInstruction has its physical successor as its successor
554 	 * (opposed to its target) as defined here.
555 	 */
556 	private static InstructionHandle[] getSuccessors(InstructionHandle instruction){
557 		Instruction inst = instruction.getInstruction();
558 
559         if (inst instanceof RET){
560 			return empty;
561 		}
562 
563 		// Terminates method normally.
564 		if (inst instanceof ReturnInstruction){
565 			return empty;
566 		}
567 
568 		// Terminates method abnormally, because JustIce mandates
569 		// subroutines not to be protected by exception handlers.
570 		if (inst instanceof ATHROW){
571 			return empty;
572 		}
573 
574         final InstructionHandle[] single = new InstructionHandle[1];
575 
576         // See method comment.
577 		if (inst instanceof JsrInstruction){
578 			single[0] = instruction.getNext();
579 			return single;
580 		}
581 
582 		if (inst instanceof GotoInstruction){
583 			single[0] = ((GotoInstruction) inst).getTarget();
584 			return single;
585 		}
586 
587 		if (inst instanceof BranchInstruction){
588 			if (inst instanceof Select){
589 				// BCEL's getTargets() returns only the non-default targets,
590 				// thanks to Eli Tilevich for reporting.
591 				InstructionHandle[] matchTargets = ((Select) inst).getTargets();
592 				InstructionHandle[] ret = new InstructionHandle[matchTargets.length+1];
593 				ret[0] = ((Select) inst).getTarget();
594 				System.arraycopy(matchTargets, 0, ret, 1, matchTargets.length);
595 				return ret;
596 			}
597 			else{
598                 final InstructionHandle[] pair = new InstructionHandle[2];
599 				pair[0] = instruction.getNext();
600 				pair[1] = ((BranchInstruction) inst).getTarget();
601 				return pair;
602 			}
603 		}
604 
605 		// default case: Fall through.
606 		single[0] = instruction.getNext();
607 		return single;
608 	}
609 
610 	/**
611 	 * Returns a String representation of this object; merely for debugging puposes.
612 	 */
613 	public String toString(){
614 		return "---\n"+subroutines.toString()+"\n---\n";
615 	}
616 }