/*
* 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.model.instructions.Instruction;
import com.sebastian_daschner.jaxrs_analyzer.model.instructions.LoadInstruction;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Determines the instructions, which are relevant for the return value of a method by simulating a runtime stack with the byte code. This class is thread-safe.
*
* @author Sebastian Daschner
*/
public class RelevantInstructionReducer {
/**
* These variable names will not be backtracked.
*/
private static final String[] VARIABLE_NAMES_TO_IGNORE = {"this"};
private final Lock lock = new ReentrantLock();
private final StackSizeSimulator stackSizeSimulator = new StackSizeSimulator();
private List<Instruction> instructions;
/**
* Returns all instructions which are somewhat "relevant" for the returned object of the method.
* The instructions are visited backwards - starting from the return statement.
* Load and Store operations are handled as well.
*
* @param instructions The instructions to reduce
* @return The relevant instructions
*/
public List<Instruction> reduceInstructions(final List<Instruction> instructions) {
lock.lock();
try {
this.instructions = instructions;
stackSizeSimulator.buildStackSizes(instructions);
return reduceInstructionsInternal(instructions);
} finally {
lock.unlock();
}
}
/**
* Returns all reduced instructions.
*
* @param instructions All instructions
* @return The relevant instructions
*/
private List<Instruction> reduceInstructionsInternal(final List<Instruction> instructions) {
final List<Instruction> visitedInstructions = new LinkedList<>();
final Set<Integer> visitedInstructionPositions = new HashSet<>();
final Set<Integer> handledLoadIndexes = new HashSet<>();
final Set<Integer> backtrackPositions = new LinkedHashSet<>(findSortedBacktrackPositions());
while (!visitedInstructionPositions.containsAll(backtrackPositions)) {
// unvisited backtrack position
final int backtrackPosition = backtrackPositions.stream().filter(pos -> !visitedInstructionPositions.contains(pos))
.findFirst().orElseThrow(IllegalStateException::new);
final List<Integer> lastVisitedPositions = stackSizeSimulator.simulateStatementBackwards(backtrackPosition);
final List<Instruction> lastVisitedInstructions = lastVisitedPositions.stream().map(instructions::get).collect(Collectors.toList());
visitedInstructionPositions.addAll(lastVisitedPositions);
visitedInstructions.addAll(lastVisitedInstructions);
// unhandled load indexes
final Set<Integer> unhandledLoadIndexes = findUnhandledLoadIndexes(handledLoadIndexes, lastVisitedInstructions);
// for each load occurrence index -> find load/store backtrack positions (reverse order matters here)
final SortedSet<Integer> loadStoreBacktrackPositions = findLoadStoreBacktrackPositions(unhandledLoadIndexes);
handledLoadIndexes.addAll(unhandledLoadIndexes);
loadStoreBacktrackPositions.stream().forEach(backtrackPositions::add);
}
// sort in method natural order
Collections.reverse(visitedInstructions);
return visitedInstructions;
}
private List<Integer> findSortedBacktrackPositions() {
final List<Integer> startPositions = new LinkedList<>(InstructionFinder.findReturnsAndThrows(instructions));
// start with last return
Collections.sort(startPositions, Comparator.reverseOrder());
return startPositions;
}
/**
* Searches for load indexes in the {@code lastVisitedInstructions} which are not contained in {@code handledLoadIndexes}.
*
* @param handledLoadIndexes The load indexed which have been handled so far
* @param lastVisitedInstructions The last visited instructions
* @return The unhandled load indexes
*/
private Set<Integer> findUnhandledLoadIndexes(final Set<Integer> handledLoadIndexes, final List<Instruction> lastVisitedInstructions) {
final Set<Integer> lastLoadIndexes = InstructionFinder.findLoadIndexes(lastVisitedInstructions, RelevantInstructionReducer::isLoadIgnored);
return lastLoadIndexes.stream().filter(k -> !handledLoadIndexes.contains(k)).collect(Collectors.toSet());
}
/**
* Checks if the given LOAD instruction should be ignored for backtracking.
*
* @param instruction The instruction to check
* @return {@code true} if the LOAD instruction will be ignored
*/
private static boolean isLoadIgnored(final LoadInstruction instruction) {
return Stream.of(VARIABLE_NAMES_TO_IGNORE).anyMatch(instruction.getName()::equals);
}
/**
* Returns all backtrack positions of the given LOAD / STORE indexes in the instructions.
* The backtrack positions of both LOAD and store instructions are the next positions where the runtime stack size is {@code 0}.
*
* @param unhandledLoadIndexes The LOAD/STORE indexes to find
* @return The backtrack positions of the LOAD / STORE indexes
*/
private SortedSet<Integer> findLoadStoreBacktrackPositions(final Set<Integer> unhandledLoadIndexes) {
return unhandledLoadIndexes.stream()
.map(index -> stackSizeSimulator.findLoadStoreBacktrackPositions(InstructionFinder.findLoadStores(index, instructions)))
.collect(() -> new TreeSet<>(Comparator.reverseOrder()), Set::addAll, Set::addAll);
}
}