de.unisb.cs.st.javaslicer.slicing.DirectSlicer.java Source code

Java tutorial

Introduction

Here is the source code for de.unisb.cs.st.javaslicer.slicing.DirectSlicer.java

Source

/** License information:
 *    Component: javaslicer-core
 *    Package:   de.unisb.cs.st.javaslicer.slicing
 *    Class:     DirectSlicer
 *    Filename:  javaslicer-core/src/main/java/de/unisb/cs/st/javaslicer/slicing/DirectSlicer.java
 *
 * This file is part of the JavaSlicer tool, developed by Clemens Hammacher at Saarland University.
 * See http://www.st.cs.uni-saarland.de/javaslicer/ for more information.
 *
 * JavaSlicer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JavaSlicer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with JavaSlicer. If not, see <http://www.gnu.org/licenses/>.
 */
package de.unisb.cs.st.javaslicer.slicing;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.objectweb.asm.Opcodes;

import de.hammacher.util.maps.IntegerMap;
import de.unisb.cs.st.javaslicer.common.classRepresentation.Instruction;
import de.unisb.cs.st.javaslicer.common.classRepresentation.InstructionInstance;
import de.unisb.cs.st.javaslicer.common.classRepresentation.InstructionType;
import de.unisb.cs.st.javaslicer.common.classRepresentation.ReadMethod;
import de.unisb.cs.st.javaslicer.common.classRepresentation.instructions.LabelMarker;
import de.unisb.cs.st.javaslicer.common.progress.ConsoleProgressMonitor;
import de.unisb.cs.st.javaslicer.common.progress.ProgressMonitor;
import de.unisb.cs.st.javaslicer.controlflowanalysis.ControlFlowAnalyser;
import de.unisb.cs.st.javaslicer.instructionSimulation.DynamicInformation;
import de.unisb.cs.st.javaslicer.instructionSimulation.SimulationEnvironment;
import de.unisb.cs.st.javaslicer.instructionSimulation.Simulator;
import de.unisb.cs.st.javaslicer.traceResult.BackwardTraceIterator;
import de.unisb.cs.st.javaslicer.traceResult.ThreadId;
import de.unisb.cs.st.javaslicer.traceResult.TraceResult;
import de.unisb.cs.st.javaslicer.variables.LocalVariable;
import de.unisb.cs.st.javaslicer.variables.StackEntry;
import de.unisb.cs.st.javaslicer.variables.Variable;

/**
 * This is the first dynamic slicer I wrote.
 * It is much faster than the new version which uses the more general
 * dependences extractor.
 *
 * I try to maintain both versions, also to verify each other's results.
 *
 * You can use this version if you are just interested in the dynamic
 * slice as a set of bytecode instructions.
 *
 * @author Clemens Hammacher
 */
public class DirectSlicer implements Opcodes {

    private final TraceResult trace;
    private final Simulator<InstructionInstance> simulator;
    private final List<ProgressMonitor> progressMonitors = new ArrayList<ProgressMonitor>(1);

    public DirectSlicer(TraceResult trace) {
        this.trace = trace;
        this.simulator = new Simulator<InstructionInstance>(trace);
    }

    public static void main(String[] args) {
        Options options = createOptions();
        CommandLineParser parser = new GnuParser();
        CommandLine cmdLine;

        try {
            cmdLine = parser.parse(options, args, true);
        } catch (ParseException e) {
            System.err.println("Error parsing the command line arguments: " + e.getMessage());
            return;
        }

        if (cmdLine.hasOption('h')) {
            printHelp(options, System.out);
            System.exit(0);
        }

        String[] additionalArgs = cmdLine.getArgs();
        if (additionalArgs.length != 2) {
            printHelp(options, System.err);
            System.exit(-1);
        }
        File traceFile = new File(additionalArgs[0]);
        String slicingCriterionString = additionalArgs[1];

        Long threadId = null;
        if (cmdLine.hasOption('t')) {
            try {
                threadId = Long.parseLong(cmdLine.getOptionValue('t'));
            } catch (NumberFormatException e) {
                System.err.println("Illegal thread id: " + cmdLine.getOptionValue('t'));
                System.exit(-1);
            }
        }

        TraceResult trace;
        try {
            trace = TraceResult.readFrom(traceFile);
        } catch (IOException e) {
            System.err.format("Could not read the trace file \"%s\": %s%n", traceFile, e);
            System.exit(-1);
            return;
        }

        List<SlicingCriterion> sc = null;
        try {
            sc = StaticSlicingCriterion.parseAll(slicingCriterionString, trace.getReadClasses());
        } catch (IllegalArgumentException e) {
            System.err.println("Error parsing slicing criterion: " + e.getMessage());
            System.exit(-1);
            return;
        }

        List<ThreadId> threads = trace.getThreads();
        if (threads.size() == 0) {
            System.err.println("The trace file contains no tracing information.");
            System.exit(-1);
        }

        ThreadId tracing = null;
        for (ThreadId t : threads) {
            if (threadId == null) {
                if ("main".equals(t.getThreadName())
                        && (tracing == null || t.getJavaThreadId() < tracing.getJavaThreadId()))
                    tracing = t;
            } else if (t.getJavaThreadId() == threadId.longValue()) {
                tracing = t;
            }
        }

        if (tracing == null) {
            System.err.println(threadId == null ? "Couldn't find the main thread."
                    : "The thread you specified was not found.");
            System.exit(-1);
            return;
        }

        long startTime = System.nanoTime();
        DirectSlicer slicer = new DirectSlicer(trace);
        if (cmdLine.hasOption("--progress"))
            slicer.addProgressMonitor(new ConsoleProgressMonitor());
        Set<Instruction> slice = slicer.getDynamicSlice(tracing, sc);
        long endTime = System.nanoTime();

        List<Instruction> sliceList = new ArrayList<Instruction>(slice);
        Collections.sort(sliceList);

        System.out.println("The dynamic slice for criterion " + sc + ":");
        for (Instruction insn : sliceList) {
            System.out.format((Locale) null, "%s.%s:%d %s%n", insn.getMethod().getReadClass().getName(),
                    insn.getMethod().getName(), insn.getLineNumber(), insn.toString());
        }
        System.out.format((Locale) null, "%nSlice consists of %d bytecode instructions.%n", sliceList.size());
        System.out.format((Locale) null, "Computation took %.2f seconds.%n", 1e-9 * (endTime - startTime));
    }

    private void addProgressMonitor(ProgressMonitor progressMonitor) {
        this.progressMonitors.add(progressMonitor);
    }

    public Set<Instruction> getDynamicSlice(ThreadId threadId, List<SlicingCriterion> sc) {
        BackwardTraceIterator<InstructionInstance> backwardInsnItr = this.trace.getBackwardIterator(threadId, null);

        IntegerMap<Set<Instruction>> controlDependences = new IntegerMap<Set<Instruction>>();

        Set<Variable> interestingVariables = new HashSet<Variable>();
        Set<Instruction> dynamicSlice = new HashSet<Instruction>();

        long nextFrameNr = 0;
        int stackDepth = 0;

        List<ReadMethod> initialStackMethods = backwardInsnItr.getInitialStackMethods();

        int allocStack = initialStackMethods.size() + 1;
        allocStack = Integer.highestOneBit(allocStack) * 2;

        Instruction[] atCatchBlockStart = new Instruction[allocStack];
        boolean[] throwsException = new boolean[allocStack];
        boolean[] interruptedControlFlow = new boolean[allocStack];
        /**
         * <code>true</code> iff this frame was aborted abnormally (NOT by a RETURN
         * instruction)
         */
        boolean[] abnormalTermination = new boolean[allocStack];
        /**
         * is set to true if the methods entry label has been passed
         */
        boolean[] finished = new boolean[allocStack];
        int[] opStack = new int[allocStack];
        int[] minOpStack = new int[allocStack];
        long[] frames = new long[allocStack];
        Instruction[] lastInstruction = new Instruction[allocStack];
        @SuppressWarnings("unchecked")
        HashSet<Instruction>[] interestingInstructions = (HashSet<Instruction>[]) new HashSet<?>[allocStack];
        StackEntry[][] cachedStackEntries = new StackEntry[allocStack][];
        LocalVariable[][] cachedLocalVariables = new LocalVariable[allocStack][];
        ReadMethod[] method = new ReadMethod[allocStack];

        for (ReadMethod method0 : initialStackMethods) {
            ++stackDepth;
            method[stackDepth] = method0;
            interruptedControlFlow[stackDepth] = true;
            frames[stackDepth] = nextFrameNr++;
        }

        for (int i = 1; i < allocStack; ++i) {
            interestingInstructions[i] = new HashSet<Instruction>();
            cachedStackEntries[i] = new StackEntry[8];
            cachedLocalVariables[i] = new LocalVariable[8];
        }

        SimulationEnvironment simEnv = new SimulationEnvironment(frames, opStack, minOpStack, cachedStackEntries,
                cachedLocalVariables, throwsException, lastInstruction, method, interruptedControlFlow);

        List<SlicingCriterionInstance> slicingCriteria;
        if (sc.isEmpty())
            slicingCriteria = Collections.emptyList();
        else if (sc.size() == 1)
            slicingCriteria = Collections.singletonList(sc.get(0).getInstance());
        else {
            slicingCriteria = new ArrayList<SlicingCriterionInstance>(sc.size());
            for (SlicingCriterion crit : sc)
                slicingCriteria.add(crit.getInstance());
        }

        for (ProgressMonitor mon : this.progressMonitors)
            mon.start(backwardInsnItr);
        try {
            @SuppressWarnings("unchecked")
            Set<Variable>[] matchedCriterionVariables = (Set<Variable>[]) new Set<?>[8];

            while (backwardInsnItr.hasNext()) {
                InstructionInstance instance = backwardInsnItr.next();
                Instruction instruction = instance.getInstruction();

                int newStackDepth = instance.getStackDepth();
                assert newStackDepth > 0;

                simEnv.removedMethod = null;
                boolean reenter = false;
                if (newStackDepth != stackDepth
                        || (reenter = finished[stackDepth] || method[stackDepth] != instruction.getMethod())) {
                    if (newStackDepth >= stackDepth) {
                        // in all steps, the stackDepth can change by at most 1 (except for the very first instruction)
                        assert newStackDepth == stackDepth + 1 || stackDepth == 0 || reenter;
                        if (newStackDepth >= atCatchBlockStart.length) {
                            int oldLen = atCatchBlockStart.length;
                            int newLen = oldLen == 0 ? 8 : 2 * oldLen;
                            atCatchBlockStart = Arrays.copyOf(atCatchBlockStart, newLen);
                            throwsException = Arrays.copyOf(throwsException, newLen);
                            interruptedControlFlow = Arrays.copyOf(interruptedControlFlow, newLen);
                            abnormalTermination = Arrays.copyOf(abnormalTermination, newLen);
                            finished = Arrays.copyOf(finished, newLen);
                            opStack = Arrays.copyOf(opStack, newLen);
                            minOpStack = Arrays.copyOf(minOpStack, newLen);
                            frames = Arrays.copyOf(frames, newLen);
                            interestingInstructions = Arrays.copyOf(interestingInstructions, newLen);
                            lastInstruction = Arrays.copyOf(lastInstruction, newLen);
                            cachedStackEntries = Arrays.copyOf(cachedStackEntries, newLen);
                            cachedLocalVariables = Arrays.copyOf(cachedLocalVariables, newLen);
                            method = Arrays.copyOf(method, newLen);
                            for (int i = oldLen; i < newLen; ++i) {
                                interestingInstructions[i] = new HashSet<Instruction>();
                                cachedStackEntries[i] = new StackEntry[8];
                                cachedLocalVariables[i] = new LocalVariable[8];
                            }
                            simEnv = new SimulationEnvironment(frames, opStack, minOpStack, cachedStackEntries,
                                    cachedLocalVariables, throwsException, lastInstruction, method,
                                    interruptedControlFlow);
                        }
                        frames[newStackDepth] = nextFrameNr++;
                        method[newStackDepth] = instruction.getMethod();

                        atCatchBlockStart[newStackDepth] = null;
                        if (instruction == method[newStackDepth].getAbnormalTerminationLabel()) {
                            throwsException[newStackDepth] = interruptedControlFlow[newStackDepth] = abnormalTermination[newStackDepth] = true;
                            interruptedControlFlow[stackDepth] = true;
                        } else {
                            throwsException[newStackDepth] = interruptedControlFlow[newStackDepth] = abnormalTermination[newStackDepth] = false;
                        }
                        finished[newStackDepth] = false;
                        opStack[newStackDepth] = 0;
                        minOpStack[newStackDepth] = 0;
                        interestingInstructions[newStackDepth].clear();
                        if (cachedLocalVariables[newStackDepth].length > 128)
                            cachedLocalVariables[newStackDepth] = new LocalVariable[8];
                        else
                            Arrays.fill(cachedLocalVariables[newStackDepth], null);
                        if (cachedStackEntries[newStackDepth].length > 128)
                            cachedStackEntries[newStackDepth] = new StackEntry[8];
                        else
                            Arrays.fill(cachedStackEntries[newStackDepth], null);
                    } else {
                        assert newStackDepth == stackDepth - 1;
                        simEnv.removedMethod = method[stackDepth];
                    }
                }
                stackDepth = newStackDepth;

                if (atCatchBlockStart[stackDepth] != null)
                    throwsException[stackDepth] = true;

                if (instruction == instruction.getMethod().getMethodEntryLabel())
                    finished[stackDepth] = true;
                lastInstruction[stackDepth] = instruction;

                DynamicInformation dynInfo = this.simulator.simulateInstruction(instance, simEnv);

                if (simEnv.removedMethod != null && !interestingInstructions[stackDepth + 1].isEmpty()) {
                    // ok, we have a control dependence since the method was called by (or for) this instruction
                    // checking if this is the instr. that directly called the method is impossible
                    dynamicSlice.add(instruction);
                    interestingInstructions[stackDepth].add(instruction);
                }

                if (matchedCriterionVariables.length <= stackDepth)
                    matchedCriterionVariables = Arrays.copyOf(matchedCriterionVariables,
                            2 * Math.max(stackDepth, matchedCriterionVariables.length));
                for (SlicingCriterionInstance crit : slicingCriteria) {
                    if (crit.matches(instance)) {
                        if (matchedCriterionVariables[stackDepth] == null)
                            matchedCriterionVariables[stackDepth] = new HashSet<Variable>();
                        if (crit.matchAllData()) {
                            matchedCriterionVariables[stackDepth].removeAll(dynInfo.getDefinedVariables());
                            matchedCriterionVariables[stackDepth].addAll(dynInfo.getUsedVariables());
                            dynamicSlice.add(instruction);
                            interestingInstructions[stackDepth].add(instance.getInstruction());
                        } else if (crit.hasLocalVariables()) {
                            for (de.unisb.cs.st.javaslicer.common.classRepresentation.LocalVariable var : crit
                                    .getLocalVariables())
                                interestingVariables.add(simEnv.getLocalVariable(stackDepth, var.getIndex()));
                        } else {
                            interestingInstructions[stackDepth].add(instance.getInstruction());
                            dynamicSlice.add(instruction);
                        }
                    } else if (matchedCriterionVariables[stackDepth] != null) {
                        interestingVariables.addAll(matchedCriterionVariables[stackDepth]);
                        matchedCriterionVariables[stackDepth] = null;
                    }
                }

                boolean isExceptionsThrowingInstance = throwsException[stackDepth]
                        && (instruction.getType() != InstructionType.LABEL
                                || !((LabelMarker) instruction).isAdditionalLabel())
                        && (instruction.getOpcode() != Opcodes.GOTO);
                if (!interestingInstructions[stackDepth].isEmpty() || isExceptionsThrowingInstance) {
                    Set<Instruction> instrControlDependences = controlDependences.get(instruction.getIndex());
                    if (instrControlDependences == null) {
                        computeControlDependences(instruction.getMethod(), controlDependences);
                        instrControlDependences = controlDependences.get(instruction.getIndex());
                        assert instrControlDependences != null;
                    }
                    // get all interesting instructions, that are dependent on the current one
                    Set<Instruction> dependantInterestingInstructions = intersect(instrControlDependences,
                            interestingInstructions[stackDepth]);
                    if (isExceptionsThrowingInstance) {
                        throwsException[stackDepth] = false;
                        // in this case, we have an additional control dependence from the catching to
                        // the throwing instruction, and a data dependence on the thrown instruction
                        for (int i = stackDepth; i > 0; --i) {
                            if (atCatchBlockStart[i] != null) {
                                if (interestingInstructions[i].contains(atCatchBlockStart[i])) {
                                    if (dependantInterestingInstructions.isEmpty())
                                        dependantInterestingInstructions = Collections
                                                .singleton(atCatchBlockStart[i]);
                                    else
                                        dependantInterestingInstructions.add(atCatchBlockStart[i]);
                                }
                                atCatchBlockStart[i] = null;

                                // data dependence:
                                // (the stack height has already been decremented when entering the catch block)
                                Variable definedException = simEnv.getOpStackEntry(i, opStack[i]);
                                if (interestingVariables.contains(definedException)) {
                                    interestingInstructions[stackDepth].add(instruction);
                                    dynamicSlice.add(instruction);
                                    interestingVariables.remove(definedException);
                                    interestingVariables.addAll(dynInfo.getUsedVariables());
                                }

                                break;
                            }
                        }
                    }
                    if (!dependantInterestingInstructions.isEmpty()) {
                        dynamicSlice.add(instruction);
                        interestingInstructions[stackDepth].removeAll(dependantInterestingInstructions);
                        interestingInstructions[stackDepth].add(instruction);
                        interestingVariables.addAll(dynInfo.getUsedVariables());
                    }
                }

                if (!interestingVariables.isEmpty()) {
                    for (Variable definedVariable : dynInfo.getDefinedVariables()) {
                        if (interestingVariables.contains(definedVariable)) {
                            interestingInstructions[stackDepth].add(instruction);
                            dynamicSlice.add(instruction);
                            interestingVariables.remove(definedVariable);
                            interestingVariables.addAll(dynInfo.getUsedVariables(definedVariable));
                        }
                    }
                }

                if (dynInfo.isCatchBlock()) {
                    atCatchBlockStart[stackDepth] = instance.getInstruction();
                    interruptedControlFlow[stackDepth] = true;
                } else if (atCatchBlockStart[stackDepth] != null) {
                    atCatchBlockStart[stackDepth] = null;
                }

            }
        } finally {
            for (ProgressMonitor mon : this.progressMonitors)
                mon.end();
        }

        for (Iterator<Instruction> it = dynamicSlice.iterator(); it.hasNext();) {
            Instruction instr = it.next();
            if (instr.getType() == InstructionType.LABEL || instr.getOpcode() == Opcodes.GOTO)
                it.remove();
        }

        return dynamicSlice;
    }

    private void computeControlDependences(ReadMethod method, IntegerMap<Set<Instruction>> controlDependences) {
        Map<Instruction, Set<Instruction>> deps = ControlFlowAnalyser.getInstance()
                .getInvControlDependences(method);
        for (Entry<Instruction, Set<Instruction>> entry : deps.entrySet()) {
            int index = entry.getKey().getIndex();
            assert !controlDependences.containsKey(index);
            controlDependences.put(index, entry.getValue());
        }
    }

    private static <T> Set<T> intersect(Set<T> set1, Set<T> set2) {
        if (set1.isEmpty() || set2.isEmpty())
            return Collections.emptySet();

        Set<T> smallerSet;
        Set<T> biggerSet;
        if (set1.size() < set2.size()) {
            smallerSet = set1;
            biggerSet = set2;
        } else {
            smallerSet = set2;
            biggerSet = set1;
        }

        Set<T> intersection = null;
        for (T obj : smallerSet) {
            if (biggerSet.contains(obj)) {
                if (intersection == null)
                    intersection = new HashSet<T>();
                intersection.add(obj);
            }
        }

        if (intersection == null)
            return Collections.emptySet();
        return intersection;
    }

    @SuppressWarnings("static-access")
    private static Options createOptions() {
        Options options = new Options();
        options.addOption(OptionBuilder.isRequired(false).withArgName("threadid").hasArg(true)
                .withDescription("thread id to select for slicing (default: main thread)").withLongOpt("threadid")
                .create('t'));
        options.addOption(OptionBuilder.isRequired(false).hasArg(false)
                .withDescription("show progress while computing the dynamic slice").withLongOpt("progress")
                .create('p'));
        options.addOption(OptionBuilder.isRequired(false).hasArg(false).withDescription("print this help and exit")
                .withLongOpt("help").create('h'));
        return options;
    }

    private static void printHelp(Options options, PrintStream out) {
        out.println("Usage: " + DirectSlicer.class.getSimpleName() + " [<options>] <file> <slicing criterion>");
        out.println("where <file> is the input trace file, and <options> may be one or more of");
        out.println("      <slicing criterion> has the form <loc>[(<occ>)]:<var>[,<loc>[(<occ>)]:<var>]*");
        out.println("      <options> may be one or more of");
        HelpFormatter formatter = new HelpFormatter();
        PrintWriter pw = new PrintWriter(out, true);
        formatter.printOptions(pw, 120, options, 5, 3);
    }

}