Commit 523c3112 authored by Carlos Galindo's avatar Carlos Galindo
Browse files

OnePassConstrainedAlg: fix two bugs (loop detection and traversed edges)

- Bug 1: if a loop was detected, but it wasn't an increasing loop, no NodeWork was generated (step 4). In the case of balanced loops (and some decreasing loops), this may lead to incorrect results.
- Bug 2: inclusion in traversedEdges was conditional on edge type, but it should be based on Constraint type, as that is what determines the PDA analysis' result. Now it doesn't include edges with ignored edge constraints. Previously, this behaviour excluded Summary edges with ListComprehensionConstraints (generated as external in SummaryEdgeGenerator#buildListsNthSummaries).
- Other changes:
  1. Shortened try-catch looking for StackOverflow
  2. Extracted loop detection from for-loop, adding a check to avoid loop detection if newConstraintsList is empty.
  3. Moved traversedEdges update logic to PDA, as it is closely linked to PDA#isIncreasingLoop.
  4. Reformatted file (whitespace and indentation).
parent e13622c1
Loading
Loading
Loading
Loading
+48 −86
Original line number Diff line number Diff line
@@ -18,10 +18,8 @@

package edg.slicing;

import edg.constraint.AccessConstraint;
import edg.constraint.Constraints;
import edg.constraint.EdgeConstraint;
import edg.constraint.LiteralConstraint;
import edg.graph.EDG;
import edg.graph.Edge;
import edg.graph.LAST.Direction;
@@ -31,18 +29,16 @@ 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.*;

public class OnePassConstrainedAlgorithm implements SlicingAlgorithm
{
public class OnePassConstrainedAlgorithm implements SlicingAlgorithm {
	protected final EDG edg;
	public OnePassConstrainedAlgorithm(EDG edg) { this.edg = edg; }

	public Set<Node> slice(Node node)
	{
	public OnePassConstrainedAlgorithm(EDG edg) {
		this.edg = edg;
	}

	public Set<Node> slice(Node node) {
		final Set<Node> slice = new HashSet<>();
		if (node == null)
			return slice;
@@ -61,10 +57,8 @@ public class OnePassConstrainedAlgorithm 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 +67,7 @@ public class OnePassConstrainedAlgorithm 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,8 +75,7 @@ public class OnePassConstrainedAlgorithm 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();
@@ -114,9 +106,7 @@ public class OnePassConstrainedAlgorithm implements SlicingAlgorithm
		return newWorks;
	}

	protected List<Work> processWork(Phase phase, EdgeWork work)
	{
		final List<Work> newWorks = new LinkedList<>();
	protected List<Work> processWork(Phase phase, EdgeWork work) {
		final Node initialNode = work.getInitialNode();
		final Edge currentEdge = work.getCurrentEdge();
		final EdgeList traversedEdges = work.getTraversedEdges();
@@ -126,74 +116,46 @@ public class OnePassConstrainedAlgorithm implements SlicingAlgorithm
		// 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;
			return Collections.emptyList();
		if (phase == Phase.SummaryGeneration && (edgeType == Edge.Type.Input || edgeType == Edge.Type.Output))
			return newWorks;
			return Collections.emptyList();
		// Do not traverse non-traversable edges
		if (!currentEdge.isTraversable())
			return newWorks;
			return Collections.emptyList();

		int idTo = edg.getEdgeTarget(currentEdge).getId();
		int idFrom = edg.getEdgeSource(currentEdge).getId();

		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;

		// THIS STATEMENT MAY LAUNCH A StackOverflowError EMPTYING THE STACK (k-limiting X elements => X = Config.MAX_STACK_SIZE)
		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");
			// STACK FULL => EMPTY THE CONSTRAINTS LIST (*)
			return List.of(new NodeWork(initialNode, nextNode, new Constraints(), edgeType));
		}

			// THIS STATEMENT MAY LAUNCH A StackOverflowError EMPTYING THE STACK (k-limiting 20 elements => Config.MAX_STACK_SIZE = 20;)
			final List<Constraints> newConstraintsList = constraint.resolve(phase, edg, currentEdge, constraintsClone, topConstraint, 0);
			for (Constraints newConstraints : newConstraintsList) {
				if (traversedEdges.contains(currentEdge)) { // IF WE TRAVERSE THE SAME EDGE TWICE WHILE TRAVERSING FLOW AND VALUE EDGES...
		// IF THE CONSTRAINT ALLOWS TRAVERSAL AND WE TRAVERSE THE SAME EDGE TWICE WHILE TRAVERSING FLOW AND VALUE EDGES...
		if (!newConstraintsList.isEmpty() && traversedEdges.contains(currentEdge)) {
			List<Edge> loopEdges = traversedEdges.loopIterable(currentEdge); // 1) WE EXTRACT THE EDGES OF THE LOOP
			boolean isIncreasingLoop = PDA.isIncreasingLoop(loopEdges);      // 2) WE CALL THE PDA TO EVALUATE THE LOOP
			if (isIncreasingLoop) {                                          // 3) IF THE LOOP IS INCREASING...
				// REMOVE STACK (ASTERISK CONSTRAINT) AND CONTINUE TRAVERSAL ADDING THE EDGE TO traversedEdges
				// ACTS AS AN ASTERISK BUT DOES NOT REPLACE THE CONSTRAINT IN THE ARC
						// currentEdge.setVisibleConstraint(AsteriskConstraint.getConstraint());
						newWorks.add(new NodeWork(initialNode, nextNode, new EdgeList(currentEdge), new Constraints(), edgeType));
				return List.of(new NodeWork(initialNode, nextNode, new EdgeList(currentEdge), new Constraints(), edgeType));
				// WE CAN OPTIONALLY MODIFY IT TO OPTIMISE FUTURE TRAVERSALS TOO
					} // 4) IF THE LOOP WAS NOT INCREASING RESTART TRAVERSED EDGES (DO NOTHING)
				} else {
					final EdgeList traversedParam;
					switch (currentEdge.getType()) {
						case Flow: // SUMMARY EDGES MAY GENERATE IT TOO, BUT WE ARE INTRAPROCEDURAL NOW
						case Value:
							if ((constraint instanceof AccessConstraint || !traversedEdges.isEmpty())
									&& !(constraint instanceof LiteralConstraint))
								traversedParam = new EdgeList(traversedEdges, currentEdge);
							else
								traversedParam = EdgeList.emptyList();
							break;
						case Input:
						case Output:
							if (!traversedEdges.isEmpty())
								traversedParam = new EdgeList(traversedEdges, currentEdge);
							else
								traversedParam = EdgeList.emptyList();
							break;
						default:
							traversedParam = EdgeList.emptyList();
							break;
					}
					newWorks.add(new NodeWork(initialNode, nextNode, traversedParam, newConstraints, edgeType));
				}
			}

			return newWorks;
		}
		catch (StackOverflowError e)
		{
			if (!phase.isInstanceof(Phase.Slicing))
				throw new RuntimeException("Constraint situation not contemplated");
			// STACK FULL => EMPTY THE CONSTRAINTS LIST (*)
			newWorks.add(new NodeWork(initialNode, nextNode, new Constraints(), edgeType));
				// currentEdge.setVisibleConstraint(AsteriskConstraint.getConstraint());
			} // 4) IF THE LOOP WAS NOT INCREASING, TRAVERSE NORMALLY
		}

		List<Work> newWorks = new ArrayList<>(newConstraintsList.size());
		for (Constraints newConstraints : newConstraintsList)
			newWorks.add(new NodeWork(initialNode, nextNode, PDA.updateTraversedEdges(work), newConstraints, edgeType));
		return newWorks;
	}
}
+37 −0
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.constraint.*;
import edg.constraint.AccessConstraint.Operation;
import edg.graph.Edge;
import edg.work.EdgeWork;

import java.util.List;
import java.util.Stack;
@@ -58,4 +77,22 @@ public class PDA {
                return false;
        return true;
    }

    /**
     * Decides whether to add or not an edge to the list of traversed edges,
     * for the purposes of loop detection and PDA application.
     * @param work The current EdgeWork being traversed, which includes the list of traversed edges and the current edge.
     * @return The modified EdgeList according to the traversal rules and the interests of the PDA.
     * @implNote This method's behaviour should be synced with the analysis of the PDA at {@link #isIncreasingLoop(List)}.
     */
    public static EdgeList updateTraversedEdges(EdgeWork work) {
        Edge e = work.getCurrentEdge();
        Constraint c = e.getConstraint();
        if (c instanceof AccessConstraint)
            return new EdgeList(work.getTraversedEdges(), e);
        else if (c instanceof EmptyConstraint || c instanceof PhaseConstraint)
            return work.getTraversedEdges();
        else
            return EdgeList.emptyList();
    }
}