/* * Copyright 2010-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * ASM: a very small and fast Java bytecode manipulation framework * Copyright (c) 2000-2011 INRIA, France Telecom * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. * */ package org.jetbrains.kotlin.codegen.inline; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.org.objectweb.asm.*; import java.util.*; /** * This class is based on `org.objectweb.asm.MethodWriter` * * @author Eric Bruneton * @author Eugene Kuleshov * @author Denis Zharkov */ public class MaxStackFrameSizeAndLocalsCalculator extends MaxLocalsCalculator { private static final int[] FRAME_SIZE_CHANGE_BY_OPCODE; static { // copy-pasted from org.jetbrains.org.objectweb.asm.Frame int i; int[] b = new int[202]; String s = "EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDD" + "CDCDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCD" + "CDCEEEEDDDDDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEFED" + "DDCDCDEEEEEEEEEEFEEEEEEDDEEDDEE"; for (i = 0; i < b.length; ++i) { b[i] = s.charAt(i) - 'E'; } FRAME_SIZE_CHANGE_BY_OPCODE = b; } private final LabelWrapper firstLabel; private LabelWrapper currentBlock; private LabelWrapper previousBlock; /** * The (relative) stack size after the last visited instruction. This size * is relative to the beginning of the current basic block, i.e., the true * stack size after the last visited instruction is equal to the * {@link MaxStackFrameSizeAndLocalsCalculator.LabelWrapper#inputStackSize} of the current basic block * plus <tt>stackSize</tt>. */ private int stackSize; /** * The (relative) maximum stack size after the last visited instruction. * This size is relative to the beginning of the current basic block, i.e., * the true maximum stack size after the last visited instruction is equal * to the {@link MaxStackFrameSizeAndLocalsCalculator.LabelWrapper#inputStackSize} of the current basic * block plus <tt>stackSize</tt>. */ private int maxStackSize; /** * Maximum stack size of this method. */ private int maxStack; private final Collection<ExceptionHandler> exceptionHandlers = new LinkedList<>(); private final Map<Label, LabelWrapper> labelWrappersMap = new HashMap<>(); public MaxStackFrameSizeAndLocalsCalculator(int api, int access, String descriptor, MethodVisitor mv) { super(api, access, descriptor, mv); firstLabel = getLabelWrapper(new Label()); processLabel(firstLabel.label); } @Override public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { throw new AssertionError("We don't support visitFrame because currently nobody needs"); } @Override public void visitInsn(int opcode) { increaseStackSize(FRAME_SIZE_CHANGE_BY_OPCODE[opcode]); // if opcode == ATHROW or xRETURN, ends current block (no successor) if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) { noSuccessor(); } super.visitInsn(opcode); } @Override public void visitIntInsn(int opcode, int operand) { if (opcode != Opcodes.NEWARRAY) { // updates current and max stack sizes only if it's not NEWARRAY // (stack size variation is 0 for NEWARRAY and +1 BIPUSH or SIPUSH) increaseStackSize(1); } super.visitIntInsn(opcode, operand); } @Override public void visitVarInsn(int opcode, int var) { increaseStackSize(FRAME_SIZE_CHANGE_BY_OPCODE[opcode]); super.visitVarInsn(opcode, var); } @Override public void visitTypeInsn(int opcode, @NotNull String type) { if (opcode == Opcodes.NEW) { // updates current and max stack sizes only if opcode == NEW // (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF) increaseStackSize(1); } super.visitTypeInsn(opcode, type); } @Override public void visitFieldInsn(int opcode, @NotNull String owner, @NotNull String name, @NotNull String desc) { int stackSizeVariation; // computes the stack size variation char c = desc.charAt(0); switch (opcode) { case Opcodes.GETSTATIC: stackSizeVariation = c == 'D' || c == 'J' ? 2 : 1; break; case Opcodes.PUTSTATIC: stackSizeVariation = c == 'D' || c == 'J' ? -2 : -1; break; case Opcodes.GETFIELD: stackSizeVariation = c == 'D' || c == 'J' ? 1 : 0; break; // case Constants.PUTFIELD: default: stackSizeVariation = c == 'D' || c == 'J' ? -3 : -2; break; } increaseStackSize(stackSizeVariation); super.visitFieldInsn(opcode, owner, name, desc); } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { int argSize = Type.getArgumentsAndReturnSizes(desc); int sizeVariation; if (opcode == Opcodes.INVOKESTATIC) { sizeVariation = (argSize & 0x03) - (argSize >> 2) + 1; } else { sizeVariation = (argSize & 0x03) - (argSize >> 2); } increaseStackSize(sizeVariation); super.visitMethodInsn(opcode, owner, name, desc, itf); } @Override public void visitInvokeDynamicInsn(@NotNull String name, @NotNull String desc, @NotNull Handle bsm, @NotNull Object... bsmArgs) { int argSize = Type.getArgumentsAndReturnSizes(desc); increaseStackSize((argSize & 0x03) - (argSize >> 2) + 1); super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); } @Override public void visitJumpInsn(int opcode, @NotNull Label label) { if (currentBlock != null) { // updates current stack size (max stack size unchanged // because stack size variation always negative in this // case) stackSize += FRAME_SIZE_CHANGE_BY_OPCODE[opcode]; addSuccessor(getLabelWrapper(label), stackSize); if (opcode == Opcodes.GOTO) { noSuccessor(); } } super.visitJumpInsn(opcode, label); } @Override public void visitLabel(@NotNull Label label) { processLabel(label); super.visitLabel(label); } private void processLabel(Label label) { LabelWrapper wrapper = getLabelWrapper(label); if (currentBlock != null) { // ends current block (with one new successor) currentBlock.outputStackMax = maxStackSize; addSuccessor(wrapper, stackSize); } // begins a new current block currentBlock = wrapper; // resets the relative current and max stack sizes stackSize = 0; maxStackSize = 0; if (previousBlock != null) { previousBlock.nextLabel = wrapper; } previousBlock = wrapper; } @Override public void visitLdcInsn(@NotNull Object cst) { // computes the stack size variation if (cst instanceof Long || cst instanceof Double) { increaseStackSize(2); } else { increaseStackSize(1); } super.visitLdcInsn(cst); } @Override public void visitTableSwitchInsn(int min, int max, @NotNull Label dflt, @NotNull Label... labels) { visitSwitchInsn(dflt, labels); super.visitTableSwitchInsn(min, max, dflt, labels); } @Override public void visitLookupSwitchInsn(@NotNull Label dflt, @NotNull int[] keys, @NotNull Label[] labels) { visitSwitchInsn(dflt, labels); super.visitLookupSwitchInsn(dflt, keys, labels); } private void visitSwitchInsn(Label dflt, Label[] labels) { if (currentBlock != null) { // updates current stack size (max stack size unchanged) --stackSize; // adds current block successors addSuccessor(getLabelWrapper(dflt), stackSize); for (Label label : labels) { addSuccessor(getLabelWrapper(label), stackSize); } // ends current block noSuccessor(); } } @Override public void visitMultiANewArrayInsn(@NotNull String desc, int dims) { if (currentBlock != null) { increaseStackSize(dims - 1); } super.visitMultiANewArrayInsn(desc, dims); } @Override public void visitMaxs(int maxStack, int maxLocals) { // completes the control flow graph with exception handler blocks for (ExceptionHandler handler : exceptionHandlers) { LabelWrapper l = handler.start; LabelWrapper e = handler.end; while (l != e) { l.addSuccessor(handler.handlerLabel, 0, true); l = l.nextLabel; } } /* * control flow analysis algorithm: while the block stack is not * empty, pop a block from this stack, update the max stack size, * compute the true (non relative) begin stack size of the * successors of this block, and push these successors onto the * stack (unless they have already been pushed onto the stack). * Note: by hypothesis, the {@link LabelWrapper#inputStackSize} of the * blocks in the block stack are the true (non relative) beginning * stack sizes of these blocks. */ int max = 0; Stack<LabelWrapper> stack = new Stack<>(); Set<LabelWrapper> pushed = new HashSet<>(); stack.push(firstLabel); pushed.add(firstLabel); while (!stack.empty()) { LabelWrapper current = stack.pop(); int start = current.inputStackSize; int blockMax = start + current.outputStackMax; // updates the global max stack size if (blockMax > max) { max = blockMax; } // analyzes the successors of the block for (ControlFlowEdge edge : current.successors) { LabelWrapper successor = edge.successor; if (!pushed.contains(successor)) { // computes its true beginning stack size... successor.inputStackSize = edge.isExceptional ? 1 : start + edge.outputStackSize; // ...and pushes it onto the stack pushed.add(successor); stack.push(successor); } } } this.maxStack = Math.max(this.maxStack, Math.max(maxStack, max)); super.visitMaxs(this.maxStack, maxLocals); } @Override public void visitTryCatchBlock( @NotNull Label start, @NotNull Label end, @NotNull Label handler, String type ) { ExceptionHandler exceptionHandler = new ExceptionHandler( getLabelWrapper(start), getLabelWrapper(end), getLabelWrapper(handler) ); exceptionHandlers.add(exceptionHandler); super.visitTryCatchBlock(start, end, handler, type); } private static class ExceptionHandler { private final LabelWrapper start; private final LabelWrapper end; private final LabelWrapper handlerLabel; public ExceptionHandler( LabelWrapper start, LabelWrapper end, LabelWrapper handlerLabel ) { this.start = start; this.end = end; this.handlerLabel = handlerLabel; } } private static class ControlFlowEdge { private final LabelWrapper successor; private final int outputStackSize; private final boolean isExceptional; public ControlFlowEdge(LabelWrapper successor, int outputStackSize, boolean isExceptional) { this.successor = successor; this.outputStackSize = outputStackSize; this.isExceptional = isExceptional; } } private static class LabelWrapper { private final Label label; private LabelWrapper nextLabel = null; private final Collection<ControlFlowEdge> successors = new LinkedList<>(); private int outputStackMax = 0; private int inputStackSize = 0; public LabelWrapper(Label label) { this.label = label; } private void addSuccessor(LabelWrapper successor, int outputStackSize, boolean isExceptional) { successors.add(new ControlFlowEdge(successor, outputStackSize, isExceptional)); } } // ------------------------------------------------------------------------ // Utility methods // ------------------------------------------------------------------------ private LabelWrapper getLabelWrapper(Label label) { return ContainerUtil.<Label, LabelWrapper>getOrCreate(labelWrappersMap, label, () -> new LabelWrapper(label)); } private void increaseStackSize(int variation) { updateStackSize(stackSize + variation); } private void updateStackSize(int size) { if (size > maxStackSize) { maxStackSize = size; } stackSize = size; } private void addSuccessor(LabelWrapper successor, int outputStackSize) { currentBlock.addSuccessor(successor, outputStackSize, false); } /** * Ends the current basic block. This method must be used in the case where * the current basic block does not have any successor. */ private void noSuccessor() { if (currentBlock != null) { currentBlock.outputStackMax = maxStackSize; currentBlock = null; } } }