Commit 8fb45278 authored by Carlos Galindo's avatar Carlos Galindo
Browse files

Slicing algorithms unification and overhaul.

Main motivation: AdaptedStandardAlgorithm is not an equivalent context-insensitive version of StandardAlgorithm, because it groups nodes as if it were slicing the SDG.
Secondary motivation: apply the same traversal rules (if applicable) to all slicing algorithms, instead of having different versions that haven't been updated since they stopped being used in benchmarks.
- Introduced the OnePassStandardAlgorithm, field-insensitive version of OnePassConstrainedAlgorithm.
- Document all available slicing algorithms and their quirks (javadoc on interface).
- Unified traversal rules for the EDG on SlicingAlgorithm.

Changes to specific algorithms:
- StandardAlgorithm, AdaptedStandardAlgorithm: include last edge type in traversal.
- ConstrainedAlgorithm, OnePassConstrainedAlgorithm:
  - Simplify NodeWork processing and EdgeWork constructor.
  - Upstream types of edges that can't be traversed during SummaryGeneration.
- ConstrainedAlgorithm: copy changes of commit 523c3112.
- Pseudo-predicate algorithms: unify conditional traversal on SlicingAlgorithm and simplify both algorithms.

Other, minor changes:
- Add EDG#clearConstraints, moved from OnePassConstrainedAlgorithm#slice (was commented out)
- LAST#getResFromNode: add check for null NodeInfo.
- Documentation
parent 523c3112
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -31,6 +31,11 @@ public class Constraints extends Stack<EdgeConstraint> {
				&& super.equals(object);
	}

	/**
	 * Checks whether this stack is subsumed by the other's.
	 * If two slicing states are compared with this method, the rest of the
	 * state must be equal (node, lastEdge, context...).
	 */
	public boolean isSuffix(Constraints constraints)
	{
		if (this.size() > constraints.size())
+10 −0
Original line number Diff line number Diff line
@@ -46,6 +46,16 @@ public class EDG extends LAST {
		this.getGenerationTime().setCompositeTime(last.getCompositeStructureTime());
	}

	/**
	 * When slicing with algorithms that use the PDA, they could overwrite constraints
	 * as they discover increasing loops. This method undoes those changes.
	 */
	public void clearConstraints() {
		edgeSet().parallelStream()
				.filter(e -> e.getType() == Edge.Type.Flow || e.getType() == Edge.Type.Value)
				.forEach(Edge::resetVisibleConstraint);
	}

	// ================================================= //
	// ===================== NODES ===================== //
	// ================================================= //
+19 −1
Original line number Diff line number Diff line
/*
 * EDG, a library to generate and slice Expression Dependence Graphs.
 * Copyright (c) 2021-2023. David Insa, Carlos Galindo, Sergio Pérez, Josep Silva, Salvador Tamarit.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package edg.graph;

import edg.LASTBuilder.ClassInfo;
@@ -180,7 +198,7 @@ public class LAST extends GraphWithRoot {
	 */
	public Node getResFromNode(Node node)
	{
		if (!node.getInfo().isExpression())
		if (node.getInfo() == null || !node.getInfo().isExpression())
			return node;
		if (resultFromNode.containsKey(node))
			return resultFromNode.get(node);
+65 −53
Original line number Diff line number Diff line
/*
 * EDG, a library to generate and slice Expression Dependence Graphs.
 * Copyright (c) 2021-2023. David Insa, Carlos Galindo, Sergio Pérez, Josep Silva, Salvador Tamarit.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package edg.slicing;

import edg.graph.EDG;
@@ -7,76 +25,71 @@ import edg.graph.Node;

import java.util.*;

public class AdaptedStandardAlgorithm extends StandardAlgorithm{
/**
 * A simple slicing algorithm that is context-insensitive and field-insensitive.
 * <br>
 * <b>Warning</b>: this algorithm groups nodes as if the graph was a SDG/PDG instead of an EDG.
 */
public class AdaptedStandardAlgorithm implements SlicingAlgorithm {

    protected final EDG edg;

    public AdaptedStandardAlgorithm(EDG edg) {
        super(edg);
        this.edg = edg;
    }

    public Set<Node> slice(Node slicingCriterion)
    {
    @Override
    public Set<Node> slice(Node slicingCriterion) {
        final Set<Node> slice = new HashSet<>();
        if (slicingCriterion == null)
            return slice;

        slice.add(slicingCriterion);
        this.traverse(Phase.OnePhase,slicingCriterion, slice);

        return slice;
    }

    protected void traverse(Phase phase, Node slicingCriterion, Set<Node> slice, Edge.Type... ignoreEdgeTypes) {
        final Deque<SliceState> pendingNodes = new LinkedList<>();

        for (Node n : slice)
            pendingNodes.add(new SliceState(n,null));
        final Deque<State> pendingStates = new LinkedList<>();
        Set<State> visitedStates = new HashSet<>();

        final Set<Edge.Type> ignoreEdgeTypesSet = new HashSet<>(Arrays.asList(ignoreEdgeTypes));
        State scState = new State(slicingCriterion, null);
        pendingStates.add(scState);
        visitedStates.add(scState);

        while (!pendingNodes.isEmpty())
        {
            final SliceState pendingNode = pendingNodes.removeFirst();
            final Set<Edge> nextEdges = edg.getEdges(pendingNode.getNode(), sliceDirection);
        while (!pendingStates.isEmpty()) {
            final State state = pendingStates.removeFirst();

            nextEdges.removeIf(e -> ignoreEdgeTypesSet.contains(e.getType()));
            nextEdges.removeIf(Edge::isControlFlowEdge);
            nextEdges.removeIf(e -> !e.isTraversable());

            if (pendingNode.getLastEdgeType() != null && pendingNode.getLastEdgeType() == Edge.Type.Structural)
                nextEdges.removeIf(e -> e.getType() != Edge.Type.Structural);

            for (Edge nextEdge : nextEdges)
            {
                if(phase == Phase.SummaryGeneration &&
                        (nextEdge.getType() == Edge.Type.Input || nextEdge.getType() == Edge.Type.Output))
            for (Edge edge : edg.getEdges(state.node, sliceDirection)) {
                if (!canTraverseEdgeEDG(state.node, edge, state.lastEdgeType))
                    continue;

                final Node nextNode = sliceDirection == LAST.Direction.Backwards ?
                        edg.getEdgeSource(nextEdge): edg.getEdgeTarget(nextEdge);
                if (!slice.contains(nextNode))
                {
                    Node outerStructureNode = this.getOuterCompositeNode(nextNode);
                final Node node = sliceDirection == LAST.Direction.Backwards ?
                        edg.getEdgeSource(edge) : edg.getEdgeTarget(edge);
                State nodeState = new State(node, edge.getType());
                if (!visitedStates.contains(nodeState)) {
                    Node outerStructureNode = this.getOuterCompositeNode(node);
                    if (outerStructureNode != null) {
                        List<Node> nextNodes = edg.getDescendants(outerStructureNode);
                        nextNodes.add(outerStructureNode);

                        if (nextNodes.contains(slicingCriterion))
                            nextNodes.removeIf(n -> n.getType() == Node.Type.Result);
                        nextNodes.add(edg.getResFromNode(nextNode));
                        nextNodes.add(edg.getResFromNode(node));

                        for (Node next : nextNodes) {
                            pendingNodes.addLast(new SliceState(next,nextEdge.getType()));
                            slice.add(next);
                            State descendantState = new State(next, edge.getType());
                            if (visitedStates.add(descendantState))
                                pendingStates.addLast(descendantState);
                        }
                    }
                    else {
                        pendingNodes.addLast(new SliceState(nextNode,nextEdge.getType()));
                        slice.add(nextNode);
                    } else {
                        if (visitedStates.add(nodeState))
                            pendingStates.addLast(nodeState);
                    }
                }
            }
        }

        for (State s : visitedStates)
            slice.add(s.node);

        return slice;
    }

    public Node getOuterCompositeNode(Node node) {
        Node lastDataContainerParent = null;
        Node nextParent = edg.getParent(node);
@@ -102,14 +115,13 @@ public class AdaptedStandardAlgorithm extends StandardAlgorithm{
        }
    }

    private class SliceState{
        private Node node;
        private Edge.Type lastEdgeType;
        public SliceState(Node n, Edge.Type type){
            node = n;
            lastEdgeType = type;
    private record State(Node node, Edge.Type lastEdgeType) {
        private State(Node node, Edge.Type lastEdgeType) {
            this.node = node;
            if (lastEdgeType == Edge.Type.Structural || lastEdgeType == Edge.Type.Control)
                this.lastEdgeType = lastEdgeType;
            else
                this.lastEdgeType = null;
        }
        public Node getNode() { return node; }
        public Edge.Type getLastEdgeType() { return lastEdgeType; }
    }
}
+40 −75
Original line number Diff line number Diff line
@@ -29,23 +29,20 @@ import edg.work.NodeWork;
import edg.work.Work;
import edg.work.WorkList;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.*;

/**
 * A field-sensitive slicing algorithm for the EDG, constrained
 * A field-sensitive, context-sensitive slicing algorithm for the EDG, constrained
 * by the edges traversed during the slice.
 */
public class ConstrainedAlgorithm implements SlicingAlgorithm
{
public class ConstrainedAlgorithm implements SlicingAlgorithm {
	protected final EDG edg;

	public ConstrainedAlgorithm(EDG edg) { this.edg = edg; }
	public ConstrainedAlgorithm(EDG edg) {
		this.edg = edg;
	}

	public Set<Node> slice(Node node)
	{
	public Set<Node> slice(Node node) {
		final Set<Node> slice = new HashSet<>();
		if (node == null)
			return slice;
@@ -61,10 +58,8 @@ public class ConstrainedAlgorithm implements SlicingAlgorithm
		return slice;
	}

	private void traverse(Phase phase, WorkList workList)
	{
		while (workList.hasNext())
		{
	private void traverse(Phase phase, WorkList workList) {
		while (workList.hasNext()) {
			final Work pendingWork = workList.removeNext();
			final List<Work> newWorks = this.processWork(phase, pendingWork);

@@ -73,8 +68,7 @@ public class ConstrainedAlgorithm implements SlicingAlgorithm
		}
	}

	public List<Work> processWork(Phase phase, Work work)
	{
	public List<Work> processWork(Phase phase, Work work) {
		if (work instanceof NodeWork)
			return this.processWork(phase, (NodeWork) work);
		if (work instanceof EdgeWork)
@@ -82,82 +76,53 @@ public class ConstrainedAlgorithm implements SlicingAlgorithm
		throw new RuntimeException("Work type not contemplated");
	}

	protected List<Work> processWork(Phase phase, NodeWork work)
	{
	protected List<Work> processWork(Phase phase, NodeWork work) {
		final List<Work> newWorks = new LinkedList<>();
		final Node initialNode = work.getInitialNode();
		final Node currentNode = work.getCurrentNode();
		final Constraints constraints = work.getConstraints();

		final Set<Edge> edges = edg.getEdges(currentNode, sliceDirection);
		edges.removeIf(Edge::isControlFlowEdge);
		if(phase == Phase.SummaryGeneration) {
			edges.removeIf(edge -> edge.getType() == Edge.Type.Exception);
			edges.removeIf(edge -> edge.getType() == Edge.Type.Input ||
									edge.getType() == Edge.Type.Output ||
									edge.getType() == Edge.Type.Call);
		}

		// TRAVERSAL RESTRICTION TODO: Change this restriction to construction time removing structural arcs
		// GENERATOR NODES CONTAIN VALUE EDGES THAT MUST BE TRAVERSED ONLY IF THE GENERATOR NODE IS INCLUDED BY CONTROL
		if (currentNode.getType() == Node.Type.Generator && work.getPreviousEdgeType() != Edge.Type.Control)
				edges.removeIf(edge -> edge.getType() == Edge.Type.Value);

		if (work.getPreviousEdgeType() == Edge.Type.Structural)
			edges.removeIf(edge -> edge.getType() != Edge.Type.Structural);

		final Constraints constraintsClone = (Constraints) constraints.clone();
		for (Edge edge : edges)
			newWorks.add(new EdgeWork(edg, initialNode, edge, work.getTraversedEdges(), constraintsClone));

		for (Edge edge : edg.getEdges(work.getCurrentNode(), sliceDirection))
			if (canTraverseEdgeEDG(work.getCurrentNode(), edge, work.getPreviousEdgeType())
					&& (phase != Phase.SummaryGeneration || !summaryPhaseIgnore.contains(edge.getType())))
				newWorks.add(new EdgeWork(edg, work, edge));
		return newWorks;
	}

	private List<Work> processWork(Phase phase, EdgeWork work)
	{
		final List<Work> newWorks = new LinkedList<>();
	private List<Work> processWork(Phase phase, EdgeWork work) {
		final Node initialNode = work.getInitialNode();
		final Edge currentEdge = work.getCurrentEdge();
		final EdgeList traversedEdges = work.getTraversedEdges();
		final Node nextNode = sliceDirection == Direction.Backwards ?
				edg.getEdgeSource(currentEdge) : edg.getEdgeTarget(currentEdge);

		// NECESSARY TO CONTROL THE OUTPUT EDGES WITH LET_THROUGH_CONSTRAINTS
		final Edge.Type edgeType = currentEdge.getType();
		if (phase == Phase.Input && edgeType == Edge.Type.Output)
			return newWorks;
			return Collections.emptyList();
		if (phase == Phase.Output && edgeType == Edge.Type.Input)
			return newWorks;
		if (phase == Phase.SummaryGeneration && (edgeType == Edge.Type.Input || edgeType == Edge.Type.Output))
			return newWorks;
		// Do not traverse non-traversable edges
		if (!currentEdge.isTraversable())
			return newWorks;
			return Collections.emptyList();

		try
		{
			final Constraints constraints = work.getConstraints();
			final Constraints constraintsClone = (Constraints) constraints.clone();
		final Constraints constraintsClone = (Constraints) work.getConstraints().clone();
		final EdgeConstraint constraint = currentEdge.getConstraint();
		final EdgeConstraint topConstraint = constraintsClone.isEmpty() ? null : constraintsClone.peek();
        final List<Constraints> newConstraintsList;

			final List<Constraints> newConstraintsList = constraint.resolve(phase, edg, currentEdge, constraintsClone, topConstraint, 0);

			for (Constraints newConstraints : newConstraintsList)
				if (phase == Phase.SummaryGeneration)
					newWorks.add(new NodeWork(initialNode, nextNode, new EdgeList(work.getTraversedEdges(), currentEdge), newConstraints, edgeType));
				else
					newWorks.add(new NodeWork(initialNode, nextNode, newConstraints, edgeType));

			return newWorks;
		}
		catch (StackOverflowError e)
		{
		try {
			newConstraintsList = constraint.resolve(phase, edg, currentEdge, constraintsClone, topConstraint, 0);
		} catch (StackOverflowError e) {
			if (!phase.isInstanceof(Phase.Slicing))
				throw new RuntimeException("Constraint situation not contemplated");
			newWorks.add(new NodeWork(initialNode, nextNode, new Constraints(), edgeType));
			return List.of(new NodeWork(initialNode, nextNode, new Constraints(), edgeType));
		}

		// If we can traverse the constraint, an edge is repeated and the loop formed is increasing: empty the stack.
		if (!newConstraintsList.isEmpty() && traversedEdges.contains(currentEdge))
			if (PDA.isIncreasingLoop(traversedEdges.loopIterable(currentEdge)))
				return List.of(new NodeWork(initialNode, nextNode, new EdgeList(currentEdge), new Constraints(), edgeType));

        final List<Work> newWorks = new ArrayList<>(newConstraintsList.size());
		for (Constraints newConstraints : newConstraintsList)
			if (phase == Phase.SummaryGeneration)
				newWorks.add(new NodeWork(initialNode, nextNode, new EdgeList(traversedEdges, currentEdge), newConstraints, edgeType));
			else
				newWorks.add(new NodeWork(initialNode, nextNode, PDA.updateTraversedEdges(work), newConstraints, edgeType));
		return newWorks;
	}

}
Loading