001: /*
002: * ProGuard -- shrinking, optimization, obfuscation, and preverification
003: * of Java bytecode.
004: *
005: * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
006: *
007: * This library is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU General Public License as published by the Free
009: * Software Foundation; either version 2 of the License, or (at your option)
010: * any later version.
011: *
012: * This library is distributed in the hope that it will be useful, but WITHOUT
013: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
015: * for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public License
018: * along with this library; if not, write to the Free Software Foundation,
019: * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: */
021: package proguard.classfile.attribute.visitor;
022:
023: import proguard.classfile.*;
024: import proguard.classfile.visitor.ClassPrinter;
025: import proguard.classfile.attribute.*;
026: import proguard.classfile.instruction.*;
027: import proguard.classfile.instruction.visitor.InstructionVisitor;
028: import proguard.classfile.util.SimplifiedVisitor;
029:
030: /**
031: * This AttributeVisitor computes the stack sizes at all instruction offsets
032: * of the code attributes that it visits.
033: *
034: * @author Eric Lafortune
035: */
036: public class StackSizeComputer extends SimplifiedVisitor implements
037: AttributeVisitor, InstructionVisitor, ExceptionInfoVisitor {
038: //*
039: private static final boolean DEBUG = false;
040: /*/
041: private static boolean DEBUG = true;
042: //*/
043:
044: private boolean[] evaluated = new boolean[ClassConstants.TYPICAL_CODE_LENGTH];
045: private int[] stackSizes = new int[ClassConstants.TYPICAL_CODE_LENGTH];
046:
047: private boolean exitInstructionBlock;
048:
049: private int stackSize;
050: private int maxStackSize;
051:
052: /**
053: * Returns whether the instruction at the given offset is reachable in the
054: * most recently visited code attribute.
055: */
056: public boolean isReachable(int instructionOffset) {
057: return evaluated[instructionOffset];
058: }
059:
060: /**
061: * Returns the stack size at the given instruction offset of the most
062: * recently visited code attribute.
063: */
064: public int getStackSize(int instructionOffset) {
065: if (!evaluated[instructionOffset]) {
066: throw new IllegalArgumentException(
067: "Unknown stack size at unreachable instruction offset ["
068: + instructionOffset + "]");
069: }
070:
071: return stackSizes[instructionOffset];
072: }
073:
074: /**
075: * Returns the maximum stack size of the most recently visited code attribute.
076: */
077: public int getMaxStackSize() {
078: return maxStackSize;
079: }
080:
081: // Implementations for AttributeVisitor.
082:
083: public void visitCodeAttribute(Clazz clazz, Method method,
084: CodeAttribute codeAttribute) {
085: // DEBUG =
086: // clazz.getName().equals("abc/Def") &&
087: // method.getName(clazz).equals("abc");
088:
089: // TODO: Remove this when the code has stabilized.
090: // Catch any unexpected exceptions from the actual visiting method.
091: try {
092: // Process the code.
093: visitCodeAttribute0(clazz, method, codeAttribute);
094: } catch (RuntimeException ex) {
095: System.err
096: .println("Unexpected error while computing stack sizes:");
097: System.err.println(" Class = [" + clazz.getName()
098: + "]");
099: System.err.println(" Method = ["
100: + method.getName(clazz)
101: + method.getDescriptor(clazz) + "]");
102: System.err.println(" Exception = ["
103: + ex.getClass().getName() + "] (" + ex.getMessage()
104: + ")");
105:
106: if (DEBUG) {
107: method.accept(clazz, new ClassPrinter());
108: }
109:
110: throw ex;
111: }
112: }
113:
114: public void visitCodeAttribute0(Clazz clazz, Method method,
115: CodeAttribute codeAttribute) {
116: if (DEBUG) {
117: System.out.println("StackSizeComputer: " + clazz.getName()
118: + "." + method.getName(clazz)
119: + method.getDescriptor(clazz));
120: }
121:
122: // Try to reuse the previous array.
123: int codeLength = codeAttribute.u4codeLength;
124: if (evaluated.length < codeLength) {
125: evaluated = new boolean[codeLength];
126: stackSizes = new int[codeLength];
127: } else {
128: for (int index = 0; index < codeLength; index++) {
129: evaluated[index] = false;
130: }
131: }
132:
133: // The initial stack is always empty.
134: stackSize = 0;
135: maxStackSize = 0;
136:
137: // Evaluate the instruction block starting at the entry point of the method.
138: evaluateInstructionBlock(clazz, method, codeAttribute, 0);
139:
140: // Evaluate the exception handlers.
141: codeAttribute.exceptionsAccept(clazz, method, this );
142: }
143:
144: // Implementations for InstructionVisitor.
145:
146: public void visitSimpleInstruction(Clazz clazz, Method method,
147: CodeAttribute codeAttribute, int offset,
148: SimpleInstruction simpleInstruction) {
149: byte opcode = simpleInstruction.opcode;
150:
151: // Some simple instructions exit from the current instruction block.
152: exitInstructionBlock = opcode == InstructionConstants.OP_IRETURN
153: || opcode == InstructionConstants.OP_LRETURN
154: || opcode == InstructionConstants.OP_FRETURN
155: || opcode == InstructionConstants.OP_DRETURN
156: || opcode == InstructionConstants.OP_ARETURN
157: || opcode == InstructionConstants.OP_RETURN
158: || opcode == InstructionConstants.OP_ATHROW;
159: }
160:
161: public void visitConstantInstruction(Clazz clazz, Method method,
162: CodeAttribute codeAttribute, int offset,
163: ConstantInstruction constantInstruction) {
164: // Constant pool instructions never end the current instruction block.
165: exitInstructionBlock = false;
166: }
167:
168: public void visitVariableInstruction(Clazz clazz, Method method,
169: CodeAttribute codeAttribute, int offset,
170: VariableInstruction variableInstruction) {
171: byte opcode = variableInstruction.opcode;
172:
173: // The ret instruction end the current instruction block.
174: exitInstructionBlock = opcode == InstructionConstants.OP_RET;
175: }
176:
177: public void visitBranchInstruction(Clazz clazz, Method method,
178: CodeAttribute codeAttribute, int offset,
179: BranchInstruction branchInstruction) {
180: byte opcode = branchInstruction.opcode;
181:
182: // Evaluate the target instruction blocks.
183: evaluateInstructionBlock(clazz, method, codeAttribute, offset
184: + branchInstruction.branchOffset);
185:
186: // Evaluate the instructions after a subroutine branch.
187: if (opcode == InstructionConstants.OP_JSR
188: || opcode == InstructionConstants.OP_JSR_W) {
189: // We assume subroutine calls (jsr and jsr_w instructions) don't
190: // change the stack, other than popping the return value.
191: stackSize -= 1;
192:
193: evaluateInstructionBlock(clazz, method, codeAttribute,
194: offset + branchInstruction.length(offset));
195: }
196:
197: // Some branch instructions always end the current instruction block.
198: exitInstructionBlock = opcode == InstructionConstants.OP_GOTO
199: || opcode == InstructionConstants.OP_GOTO_W
200: || opcode == InstructionConstants.OP_JSR
201: || opcode == InstructionConstants.OP_JSR_W;
202: }
203:
204: public void visitAnySwitchInstruction(Clazz clazz, Method method,
205: CodeAttribute codeAttribute, int offset,
206: SwitchInstruction switchInstruction) {
207: // Evaluate the target instruction blocks.
208:
209: // Loop over all jump offsets.
210: int[] jumpOffsets = switchInstruction.jumpOffsets;
211:
212: for (int index = 0; index < jumpOffsets.length; index++) {
213: // Evaluate the jump instruction block.
214: evaluateInstructionBlock(clazz, method, codeAttribute,
215: offset + jumpOffsets[index]);
216: }
217:
218: // Also evaluate the default instruction block.
219: evaluateInstructionBlock(clazz, method, codeAttribute, offset
220: + switchInstruction.defaultOffset);
221:
222: // The switch instruction always ends the current instruction block.
223: exitInstructionBlock = true;
224: }
225:
226: // Implementations for ExceptionInfoVisitor.
227:
228: public void visitExceptionInfo(Clazz clazz, Method method,
229: CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) {
230: if (DEBUG) {
231: System.out.println("Exception:");
232: }
233:
234: // The stack size when entering the exception handler is always 1.
235: stackSize = 1;
236:
237: // Evaluate the instruction block starting at the entry point of the
238: // exception handler.
239: evaluateInstructionBlock(clazz, method, codeAttribute,
240: exceptionInfo.u2handlerPC);
241: }
242:
243: // Small utility methods.
244:
245: /**
246: * Evaluates a block of instructions that hasn't been handled before,
247: * starting at the given offset and ending a branch instruction, a return
248: * instruction, or a throw instruction. Branch instructions are handled
249: * recursively.
250: */
251: private void evaluateInstructionBlock(Clazz clazz, Method method,
252: CodeAttribute codeAttribute, int instructionOffset) {
253: if (DEBUG) {
254: if (evaluated[instructionOffset]) {
255: System.out.println("-- (instruction block at "
256: + instructionOffset + " already evaluated)");
257: } else {
258: System.out.println("-- instruction block:");
259: }
260: }
261:
262: // Remember the initial stack size.
263: int initialStackSize = stackSize;
264:
265: // Remember the maximum stack size.
266: if (maxStackSize < stackSize) {
267: maxStackSize = stackSize;
268: }
269:
270: // Evaluate any instructions that haven't been evaluated before.
271: while (!evaluated[instructionOffset]) {
272: // Mark the instruction as evaluated.
273: evaluated[instructionOffset] = true;
274:
275: Instruction instruction = InstructionFactory.create(
276: codeAttribute.code, instructionOffset);
277:
278: if (DEBUG) {
279: int stackPushCount = instruction.stackPushCount(clazz);
280: int stackPopCount = instruction.stackPopCount(clazz);
281: System.out.println("[" + instructionOffset + "]: "
282: + stackSize + " - " + stackPopCount + " + "
283: + stackPushCount + " = "
284: + (stackSize + stackPushCount - stackPopCount)
285: + ": "
286: + instruction.toString(instructionOffset));
287: }
288:
289: // Compute the instruction's effect on the stack size.
290: stackSize -= instruction.stackPopCount(clazz);
291:
292: if (stackSize < 0) {
293: throw new IllegalArgumentException(
294: "Stack size becomes negative after instruction "
295: + instruction
296: .toString(instructionOffset)
297: + " in [" + clazz.getName() + "."
298: + method.getName(clazz)
299: + method.getDescriptor(clazz) + "]");
300: }
301:
302: stackSizes[instructionOffset] = stackSize += instruction
303: .stackPushCount(clazz);
304:
305: // Remember the maximum stack size.
306: if (maxStackSize < stackSize) {
307: maxStackSize = stackSize;
308: }
309:
310: // Remember the next instruction offset.
311: int nextInstructionOffset = instructionOffset
312: + instruction.length(instructionOffset);
313:
314: // Visit the instruction, in order to handle branches.
315: instruction.accept(clazz, method, codeAttribute,
316: instructionOffset, this );
317:
318: // Stop evaluating after a branch.
319: if (exitInstructionBlock) {
320: break;
321: }
322:
323: // Continue with the next instruction.
324: instructionOffset = nextInstructionOffset;
325:
326: if (DEBUG) {
327: if (evaluated[instructionOffset]) {
328: System.out
329: .println("-- (instruction at "
330: + instructionOffset
331: + " already evaluated)");
332: }
333: }
334: }
335:
336: // Restore the stack size for possible subsequent instruction blocks.
337: this.stackSize = initialStackSize;
338: }
339: }
|