/* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * 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/LICENSE2.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. */ package com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.reduction; import com.sebastian_daschner.jaxrs_analyzer.utils.Pair; import com.sebastian_daschner.jaxrs_analyzer.model.instructions.Instruction; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Simulates runtime stack sizes of instructions. * * @author Sebastian Daschner */ class StackSizeSimulator { private List<Pair<Integer, Integer>> stackSizes; /** * Initializes the runtime stack sizes with the given instructions. This has to be called before {@link StackSizeSimulator#simulateStatementBackwards} * * @param instructions The instructions to simulate */ void buildStackSizes(final List<Instruction> instructions) { stackSizes = new ArrayList<>(); int stackSize = 0; for (Instruction instruction : instructions) { final int previousStackSize = stackSize; stackSize += instruction.getStackSizeDifference(); if (isStackCleared(instruction)) stackSize = 0; if (stackSize < 0) { throw new IllegalStateException("Runtime stack under-flow occurred."); } stackSizes.add(Pair.of(previousStackSize, stackSize)); } } /** * Checks if the stack will be cleared on invoking the given instruction. * * @param instruction The instruction * @return {@code true} if the stack will be cleared */ private static boolean isStackCleared(final Instruction instruction) { return instruction.getType() == Instruction.InstructionType.RETURN || instruction.getType() == Instruction.InstructionType.THROW; } /** * Returns the instruction positions which are visited <i>backwards</i> from {@code backtrackPosition} * until the runtime stack is empty. * * @param backtrackPosition The backtrack position where to start * @return All positions <i>backwards</i> until the previous empty position */ List<Integer> simulateStatementBackwards(final int backtrackPosition) { // search for previous zero-position in stackSizes int currentPosition = backtrackPosition; // check against stack size before the instruction was executed while (stackSizes.get(currentPosition).getLeft() > 0) { currentPosition--; } return Stream.iterate(backtrackPosition, c -> --c).limit(backtrackPosition - currentPosition + 1) .collect(LinkedList::new, Collection::add, Collection::addAll); } /** * Returns all backtrack positions of the given instruction positions. * The backtrack positions of both LOAD and store instructions are the next positions where the runtime stack size is {@code 0}. * * @param loadStorePositions The LOAD/STORE positions * @return The backtrack positions */ Set<Integer> findLoadStoreBacktrackPositions(final Set<Integer> loadStorePositions) { // go to this or next zero-position return loadStorePositions.stream().map(this::findBacktrackPosition).collect(Collectors.toSet()); } /** * Returns the next position where the stack will be empty. * * @param position The current position * @return The next empty position */ private int findBacktrackPosition(final int position) { int currentPosition = position; // check against stack size after the instruction was executed while (stackSizes.get(currentPosition).getRight() > 0) { currentPosition++; } return currentPosition; } }