diff --git a/idealPDS/src/main/java/typestate/TransitionFunctionImpl.java b/idealPDS/src/main/java/typestate/TransitionFunctionImpl.java index 680e9114..86cfb6c4 100644 --- a/idealPDS/src/main/java/typestate/TransitionFunctionImpl.java +++ b/idealPDS/src/main/java/typestate/TransitionFunctionImpl.java @@ -23,6 +23,7 @@ import com.google.common.collect.Multimap; import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -35,7 +36,7 @@ public class TransitionFunctionImpl implements TransitionFunction { @NonNull private final Multimap stateChangeSequences; - @NonNull private final Statement stateChangeStatement; + @NonNull private final LinkedHashSet stateChangeStatements; public TransitionFunctionImpl( @NonNull Transition transition, @NonNull Statement stateChangeStatement) { @@ -43,7 +44,8 @@ public TransitionFunctionImpl( ImmutableMultimap.of( transition, new StatementSequence(new StatementSequence.Entry(stateChangeStatement, transition))); - this.stateChangeStatement = stateChangeStatement; + this.stateChangeStatements = new LinkedHashSet<>(); + stateChangeStatements.add(stateChangeStatement); } public TransitionFunctionImpl( @@ -56,14 +58,23 @@ public TransitionFunctionImpl( } this.stateChangeSequences = ImmutableMultimap.copyOf(sequencesMap); - this.stateChangeStatement = stateChangeStatement; + this.stateChangeStatements = new LinkedHashSet<>(); + stateChangeStatements.add(stateChangeStatement); } public TransitionFunctionImpl( @NonNull Multimap transitionStatementSequences, @NonNull Statement stateChangeStatement) { this.stateChangeSequences = ImmutableMultimap.copyOf(transitionStatementSequences); - this.stateChangeStatement = stateChangeStatement; + this.stateChangeStatements = new LinkedHashSet<>(); + stateChangeStatements.add(stateChangeStatement); + } + + public TransitionFunctionImpl( + @NonNull Multimap transitionStatementSequences, + @NonNull LinkedHashSet stateChangeStatements) { + this.stateChangeSequences = ImmutableMultimap.copyOf(transitionStatementSequences); + this.stateChangeStatements = new LinkedHashSet<>(stateChangeStatements); } @NonNull @@ -73,7 +84,7 @@ public Multimap getStateChangeSequences() { } public Statement getStateChangeStatement() { - return stateChangeStatement; + return stateChangeStatements.iterator().next(); } @NonNull @@ -122,7 +133,7 @@ public Weight extendWith(@NonNull Weight other) { } } } - return new TransitionFunctionImpl(result, func.stateChangeStatement); + return new TransitionFunctionImpl(result, func.getStateChangeStatement()); } @NonNull @@ -146,7 +157,7 @@ public Weight combineWith(@NonNull Weight other) { transitions.putAll(idTransition, statements); } - return new TransitionFunctionImpl(transitions, this.stateChangeStatement); + return new TransitionFunctionImpl(transitions, this.getStateChangeStatement()); } TransitionFunctionImpl func = (TransitionFunctionImpl) other; @@ -154,8 +165,11 @@ public Weight combineWith(@NonNull Weight other) { Multimap sequences = HashMultimap.create(); sequences.putAll(stateChangeSequences); sequences.putAll(func.stateChangeSequences); + LinkedHashSet mergedStateChangeStatements = new LinkedHashSet<>(); + mergedStateChangeStatements.addAll(func.stateChangeStatements); + mergedStateChangeStatements.addAll(stateChangeStatements); - return new TransitionFunctionImpl(sequences, func.stateChangeStatement); + return new TransitionFunctionImpl(sequences, mergedStateChangeStatements); } @Override @@ -169,11 +183,11 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; TransitionFunctionImpl that = (TransitionFunctionImpl) o; return Objects.equals(stateChangeSequences, that.stateChangeSequences) - && Objects.equals(stateChangeStatement, that.stateChangeStatement); + && Objects.equals(stateChangeStatements, that.stateChangeStatements); } @Override public int hashCode() { - return Objects.hash(stateChangeSequences, stateChangeStatement); + return Objects.hash(stateChangeSequences, stateChangeStatements); } } diff --git a/idealPDS/src/test/java/typestate/TransitionFunctionImplTest.java b/idealPDS/src/test/java/typestate/TransitionFunctionImplTest.java new file mode 100644 index 00000000..8037dbf6 --- /dev/null +++ b/idealPDS/src/test/java/typestate/TransitionFunctionImplTest.java @@ -0,0 +1,57 @@ +/** + * ***************************************************************************** + * Copyright (c) 2018 Fraunhofer IEM, Paderborn, Germany + *

+ * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + *

+ * SPDX-License-Identifier: EPL-2.0 + *

+ * Contributors: + * Johannes Spaeth - initial API and implementation + * ***************************************************************************** + */ +package typestate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import boomerang.scope.Method; +import boomerang.scope.Statement; +import boomerang.utils.MethodWrapper; +import java.util.Collections; +import org.junit.jupiter.api.Test; +import test.TestingFramework; +import wpds.impl.Weight; + +public class TransitionFunctionImplTest { + + @Test + public void testCombineWithCommutative() { + TestingFramework testingFramework = new TestingFramework(); + testingFramework.getFrameworkScope( + new MethodWrapper(getClass().getName(), "testCombineWithCommutative")); + Method method = testingFramework.getTestMethod(); + Statement first = method.getStatements().get(0); + Statement second = method.getStatements().get(1); + assertNotEquals(first, second); + TransitionFunctionImpl firstTransitionFunction = + new TransitionFunctionImpl(Collections.emptySet(), first); + TransitionFunctionImpl secondTransitionFunction = + new TransitionFunctionImpl(Collections.emptySet(), second); + assertNotEquals(firstTransitionFunction, secondTransitionFunction); + Weight firstSecondResult = firstTransitionFunction.combineWith(secondTransitionFunction); + Weight secondFirstResult = secondTransitionFunction.combineWith(firstTransitionFunction); + assertEquals(firstSecondResult, secondFirstResult); + // ensure that the behavior of getChangeStatement() is as in the + // non-commutative implementation (whether this behavior is "meaningful" is + // debatable, though) + assertEquals( + secondTransitionFunction.getStateChangeStatement(), + ((TransitionFunctionImpl) firstSecondResult).getStateChangeStatement()); + assertEquals( + firstTransitionFunction.getStateChangeStatement(), + ((TransitionFunctionImpl) secondFirstResult).getStateChangeStatement()); + } +} diff --git a/idealPDS/src/test/java/typestate/VectorTest.java b/idealPDS/src/test/java/typestate/VectorTest.java index 7c4a5804..43d29b85 100644 --- a/idealPDS/src/test/java/typestate/VectorTest.java +++ b/idealPDS/src/test/java/typestate/VectorTest.java @@ -119,4 +119,26 @@ public void staticAccessTest() { v.firstElement(); Assertions.mustBeInErrorState(v); } + + public void doSth(Vector x, Object element) { + if (staticallyUnknown()) { + x.add(element); + } else { + element = x.elementAt(0); + } + } + + @Test + @TestParameters(expectedSeedCount = 1, expectedAssertionCount = 1) + public void testNoStackOverflowDueToNonCommutativeCombineWith() { + Vector x = new Vector<>(); + Object element = new Object(); + x.add(element); + while (staticallyUnknown()) { + // the method call seems to be crucial for triggering the StackOverflowError + // (inlining the code from doSth does not trigger the error) + doSth(x, element); + } + Assertions.mustBeInAcceptingState(x); + } }