diff --git a/edu.cuny.hunter.streamrefactoring.core/src/edu/cuny/hunter/streamrefactoring/core/analysis/StreamStateMachine.java b/edu.cuny.hunter.streamrefactoring.core/src/edu/cuny/hunter/streamrefactoring/core/analysis/StreamStateMachine.java index fdf1321e..f2f8ffd0 100644 --- a/edu.cuny.hunter.streamrefactoring.core/src/edu/cuny/hunter/streamrefactoring/core/analysis/StreamStateMachine.java +++ b/edu.cuny.hunter.streamrefactoring.core/src/edu/cuny/hunter/streamrefactoring/core/analysis/StreamStateMachine.java @@ -22,7 +22,6 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.jdt.core.dom.MethodInvocation; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; @@ -281,114 +280,117 @@ public void start() throws IOException, CoreException, CallGraphBuilderCancelExc // the node of where the stream was declared: TODO: Can this be // somehow rewritten to get blocks corresponding to terminal // operations? - CGNode cgNode = this.getStream().getEnclosingMethodNode(); - - for (Iterator callSites = cgNode.iterateCallSites(); callSites.hasNext();) { - CallSiteReference callSiteReference = callSites.next(); - MethodReference calledMethod = callSiteReference.getDeclaredTarget(); - - // is it a terminal operation? TODO: Should this be - // cached somehow? Collection of all terminal operation - // invocations? - if (isTerminalOperation(calledMethod)) { - // get the basic block for the call. - ISSABasicBlock[] blocksForCall = cgNode.getIR().getBasicBlocksForCall(callSiteReference); - - assert blocksForCall.length == 1 : "Expecting only a single basic block for the call: " - + callSiteReference; - - for (int i = 0; i < blocksForCall.length; i++) { - ISSABasicBlock block = blocksForCall[i]; - - BasicBlockInContext blockInContext = getBasicBlockInContextForBlock( - block, cgNode, supergraph) - .orElseThrow(() -> new IllegalStateException( - "No basic block in context for block: " + block)); - - if (!terminalBlockToPossibleReceivers.containsKey(blockInContext)) { - // associate possible receivers with the - // blockInContext. - // search through each instruction in the - // block. - int processedInstructions = 0; - for (Iterator it = block.iterator(); it.hasNext();) { - SSAInstruction instruction = it.next(); - - // if it's a phi instruction. - if (instruction instanceof SSAPhiInstruction) - // skip it. The pointer analysis - // below will handle it. - continue; - - // Get the possible receivers. This - // number corresponds to the value - // number of the receiver of the method. - int valueNumberForReceiver = instruction.getUse(0); - - // it should be represented by a pointer - // key. - PointerKey pointerKey = engine.getHeapGraph().getHeapModel() - .getPointerKeyForLocal(cgNode, valueNumberForReceiver); - - // get the points to set for the - // receiver. This will give us all - // object instances that the receiver - // reference points to. - OrdinalSet pointsToSet = engine.getPointerAnalysis() - .getPointsToSet(pointerKey); - assert pointsToSet != null : "The points-to set (I think) should not be null for pointer: " - + pointerKey; - - OrdinalSet previousReceivers = terminalBlockToPossibleReceivers - .put(blockInContext, pointsToSet); - assert previousReceivers == null : "Reassociating a blockInContext: " - + blockInContext + " with a new points-to set: " + pointsToSet - + " that was originally: " + previousReceivers; - - ++processedInstructions; + Iterator cgNodes = this.getStream().getAnalysisEngine().getCallGraph().iterator(); + for (;cgNodes.hasNext();) { + CGNode cgNode = cgNodes.next(); + for (Iterator callSites = cgNode.iterateCallSites(); callSites.hasNext();) { + CallSiteReference callSiteReference = callSites.next(); + MethodReference calledMethod = callSiteReference.getDeclaredTarget(); + + // is it a terminal operation? TODO: Should this be + // cached somehow? Collection of all terminal operation + // invocations? + if (isTerminalOperation(calledMethod)) { + // get the basic block for the call. + + ISSABasicBlock[] blocksForCall = cgNode.getIR().getBasicBlocksForCall(callSiteReference); + + assert blocksForCall.length == 1 : "Expecting only a single basic block for the call: " + + callSiteReference; + + for (int i = 0; i < blocksForCall.length; i++) { + ISSABasicBlock block = blocksForCall[i]; + + BasicBlockInContext blockInContext = getBasicBlockInContextForBlock( + block, cgNode, supergraph) + .orElseThrow(() -> new IllegalStateException( + "No basic block in context for block: " + block)); + + if (!terminalBlockToPossibleReceivers.containsKey(blockInContext)) { + // associate possible receivers with the + // blockInContext. + // search through each instruction in the + // block. + int processedInstructions = 0; + for (Iterator it = block.iterator(); it.hasNext();) { + SSAInstruction instruction = it.next(); + + // if it's a phi instruction. + if (instruction instanceof SSAPhiInstruction) + // skip it. The pointer analysis + // below will handle it. + continue; + + // Get the possible receivers. This + // number corresponds to the value + // number of the receiver of the method. + int valueNumberForReceiver = instruction.getUse(0); + + // it should be represented by a pointer + // key. + PointerKey pointerKey = engine.getHeapGraph().getHeapModel() + .getPointerKeyForLocal(cgNode, valueNumberForReceiver); + + // get the points to set for the + // receiver. This will give us all + // object instances that the receiver + // reference points to. + OrdinalSet pointsToSet = engine.getPointerAnalysis() + .getPointsToSet(pointerKey); + assert pointsToSet != null : "The points-to set (I think) should not be null for pointer: " + + pointerKey; + + OrdinalSet previousReceivers = terminalBlockToPossibleReceivers + .put(blockInContext, pointsToSet); + assert previousReceivers == null : "Reassociating a blockInContext: " + + blockInContext + " with a new points-to set: " + pointsToSet + + " that was originally: " + previousReceivers; + + ++processedInstructions; + } + + assert processedInstructions == 1 : "Expecting to process one and only one instruction here."; } - assert processedInstructions == 1 : "Expecting to process one and only one instruction here."; - } - - IntSet intSet = instanceResult.getResult().getResult(blockInContext); - for (IntIterator it = intSet.intIterator(); it.hasNext();) { - int nextInt = it.next(); - - // retrieve the state set for this instance - // and block. - Map> ruleToStates = instanceBlockStateTable - .get(instanceKey, blockInContext); - - // if it doesn't yet exist. - if (ruleToStates == null) { - // allocate a new rule map. - ruleToStates = new HashMap<>(); - - // place it in the table. - instanceBlockStateTable.put(instanceKey, blockInContext, ruleToStates); - } - - Set stateSet = ruleToStates.get(rule); - - // if it does not yet exist. - if (stateSet == null) { - // allocate a new set. - stateSet = new HashSet<>(); - - // place it in the map. - ruleToStates.put(rule, stateSet); - } - - // get the facts. - Factoid factoid = instanceResult.getDomain().getMappedObject(nextInt); - if (factoid != DUMMY_ZERO) { - BaseFactoid baseFactoid = (BaseFactoid) factoid; - assert baseFactoid.instance.equals( - instanceKey) : "Sanity check that the fact instance should be the same as the instance being examined."; - - // add the encountered state to the set. - stateSet.add(baseFactoid.state); + IntSet intSet = instanceResult.getResult().getResult(blockInContext); + for (IntIterator it = intSet.intIterator(); it.hasNext();) { + int nextInt = it.next(); + + // retrieve the state set for this instance + // and block. + Map> ruleToStates = instanceBlockStateTable + .get(instanceKey, blockInContext); + + // if it doesn't yet exist. + if (ruleToStates == null) { + // allocate a new rule map. + ruleToStates = new HashMap<>(); + + // place it in the table. + instanceBlockStateTable.put(instanceKey, blockInContext, ruleToStates); + } + + Set stateSet = ruleToStates.get(rule); + + // if it does not yet exist. + if (stateSet == null) { + // allocate a new set. + stateSet = new HashSet<>(); + + // place it in the map. + ruleToStates.put(rule, stateSet); + } + + // get the facts. + Factoid factoid = instanceResult.getDomain().getMappedObject(nextInt); + if (factoid != DUMMY_ZERO) { + BaseFactoid baseFactoid = (BaseFactoid) factoid; + assert baseFactoid.instance.equals( + instanceKey) : "Sanity check that the fact instance should be the same as the instance being examined."; + + // add the encountered state to the set. + stateSet.add(baseFactoid.state); + } } } } diff --git a/edu.cuny.hunter.streamrefactoring.tests/resources/ConvertStreamToParallel/testNonInternalAPI2/in/A.java b/edu.cuny.hunter.streamrefactoring.tests/resources/ConvertStreamToParallel/testNonInternalAPI2/in/A.java new file mode 100644 index 00000000..12473904 --- /dev/null +++ b/edu.cuny.hunter.streamrefactoring.tests/resources/ConvertStreamToParallel/testNonInternalAPI2/in/A.java @@ -0,0 +1,20 @@ +package p; + +import java.util.HashSet; +import java.util.stream.*; + +import edu.cuny.hunter.streamrefactoring.annotations.*; + +class A { + + Stream m() { + Stream stream = new HashSet<>().stream().distinct(); + return stream; + } + + @EntryPoint + void n() { + Stream s = m(); + s.count(); + } +} \ No newline at end of file diff --git a/edu.cuny.hunter.streamrefactoring.tests/resources/ConvertStreamToParallel/testNonInternalAPI3/in/A.java b/edu.cuny.hunter.streamrefactoring.tests/resources/ConvertStreamToParallel/testNonInternalAPI3/in/A.java new file mode 100644 index 00000000..0cf855b9 --- /dev/null +++ b/edu.cuny.hunter.streamrefactoring.tests/resources/ConvertStreamToParallel/testNonInternalAPI3/in/A.java @@ -0,0 +1,20 @@ +package p; + +import java.util.HashSet; +import java.util.stream.*; + +import edu.cuny.hunter.streamrefactoring.annotations.*; + +class A { + + Stream m() { + Stream stream = new HashSet<>().stream(); + return stream; + } + + @EntryPoint + void n() { + Stream s = m(); + s.distinct().count(); + } +} \ No newline at end of file diff --git a/edu.cuny.hunter.streamrefactoring.tests/resources/ConvertStreamToParallel/testNonInternalAPI4/in/A.java b/edu.cuny.hunter.streamrefactoring.tests/resources/ConvertStreamToParallel/testNonInternalAPI4/in/A.java new file mode 100644 index 00000000..ef289b6f --- /dev/null +++ b/edu.cuny.hunter.streamrefactoring.tests/resources/ConvertStreamToParallel/testNonInternalAPI4/in/A.java @@ -0,0 +1,20 @@ +package p; + +import java.util.HashSet; +import java.util.stream.*; + +import edu.cuny.hunter.streamrefactoring.annotations.*; + +class A { + + Stream m() { + Stream stream = new HashSet<>().stream().sorted(); + return stream; + } + + @EntryPoint + void n() { + Stream s = m(); + s.distinct().count(); + } +} \ No newline at end of file diff --git a/edu.cuny.hunter.streamrefactoring.tests/resources/ConvertStreamToParallel/testNonInternalAPI5/in/A.java b/edu.cuny.hunter.streamrefactoring.tests/resources/ConvertStreamToParallel/testNonInternalAPI5/in/A.java new file mode 100644 index 00000000..ca5fc61b --- /dev/null +++ b/edu.cuny.hunter.streamrefactoring.tests/resources/ConvertStreamToParallel/testNonInternalAPI5/in/A.java @@ -0,0 +1,20 @@ +package p; + +import java.util.HashSet; +import java.util.stream.*; + +import edu.cuny.hunter.streamrefactoring.annotations.*; + +class A { + + Stream m() { + Stream stream = new HashSet<>().stream().parallel(); + return stream; + } + + @EntryPoint + void n() { + Stream s = m(); + s.distinct().count(); + } +} \ No newline at end of file diff --git a/edu.cuny.hunter.streamrefactoring.tests/resources/ConvertStreamToParallel/testNonInternalAPI6/in/A.java b/edu.cuny.hunter.streamrefactoring.tests/resources/ConvertStreamToParallel/testNonInternalAPI6/in/A.java new file mode 100644 index 00000000..da1fe804 --- /dev/null +++ b/edu.cuny.hunter.streamrefactoring.tests/resources/ConvertStreamToParallel/testNonInternalAPI6/in/A.java @@ -0,0 +1,23 @@ +package p; + +import java.util.HashSet; +import java.util.stream.*; + +import edu.cuny.hunter.streamrefactoring.annotations.*; + +class A { + + Stream m() { + Stream stream = new HashSet<>().stream().parallel(); + return stream; + } + + void n(Stream s) { + s.distinct().count(); + } + + @EntryPoint + public void main(String[] args) { + n(m()); + } +} \ No newline at end of file diff --git a/edu.cuny.hunter.streamrefactoring.tests/test cases/edu/cuny/hunter/streamrefactoring/ui/tests/ConvertStreamToParallelRefactoringTest.java b/edu.cuny.hunter.streamrefactoring.tests/test cases/edu/cuny/hunter/streamrefactoring/ui/tests/ConvertStreamToParallelRefactoringTest.java index 37c5511d..dfbfb320 100644 --- a/edu.cuny.hunter.streamrefactoring.tests/test cases/edu/cuny/hunter/streamrefactoring/ui/tests/ConvertStreamToParallelRefactoringTest.java +++ b/edu.cuny.hunter.streamrefactoring.tests/test cases/edu/cuny/hunter/streamrefactoring/ui/tests/ConvertStreamToParallelRefactoringTest.java @@ -12,6 +12,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -454,9 +455,60 @@ public void testNonInternalAPI() throws Exception { helper(new StreamAnalysisExpectedResult("new HashSet<>().parallelStream()", Collections.singleton(ExecutionMode.PARALLEL), Collections.singleton(Ordering.UNORDERED), false, false, false, null, null, null, RefactoringStatus.ERROR, - EnumSet.of(PreconditionFailure.NO_TERMINAL_OPERATIONS))); + EnumSet.of(PreconditionFailure.UNORDERED))); } + public void testNonInternalAPI2() throws Exception { + helper(new StreamAnalysisExpectedResult("new HashSet<>().stream()", + Collections.singleton(ExecutionMode.SEQUENTIAL), Collections.singleton(Ordering.UNORDERED), false, true, + false, Collections.singleton(TransformationAction.CONVERT_TO_PARALLEL), PreconditionSuccess.P1, + Refactoring.CONVERT_SEQUENTIAL_STREAM_TO_PARALLEL, RefactoringStatus.OK, Collections.emptySet())); + } + + public void testNonInternalAPI3() throws Exception { + helper(new StreamAnalysisExpectedResult("new HashSet<>().stream()", + Collections.singleton(ExecutionMode.SEQUENTIAL), Collections.singleton(Ordering.UNORDERED), false, true, + false, Collections.singleton(TransformationAction.CONVERT_TO_PARALLEL), PreconditionSuccess.P1, + Refactoring.CONVERT_SEQUENTIAL_STREAM_TO_PARALLEL, RefactoringStatus.OK, Collections.emptySet())); + } + + /** + * related to #126 + */ + public void testNonInternalAPI4() throws Exception { + HashSet orderings = new HashSet<>(); + orderings.add(Ordering.UNORDERED); + orderings.add(Ordering.ORDERED); + + helper(new StreamAnalysisExpectedResult("new HashSet<>().stream()", + Collections.singleton(ExecutionMode.SEQUENTIAL), orderings, false, true, false, null, null, null, + RefactoringStatus.ERROR, EnumSet.of(PreconditionFailure.INCONSISTENT_POSSIBLE_ORDERINGS))); + } + + /** + * related to #126 + */ + public void testNonInternalAPI5() throws Exception { + HashSet executionModes = new HashSet<>(); + executionModes .add(ExecutionMode.PARALLEL); + executionModes .add(ExecutionMode.SEQUENTIAL); + helper(new StreamAnalysisExpectedResult("new HashSet<>().stream()", executionModes, + Collections.singleton(Ordering.UNORDERED), false, true, false, null, null, null, + RefactoringStatus.ERROR, EnumSet.of(PreconditionFailure.INCONSISTENT_POSSIBLE_EXECUTION_MODES))); + } + + /** + * related to #126 + */ + public void testNonInternalAPI6() throws Exception { + HashSet executionModes = new HashSet<>(); + executionModes.add(ExecutionMode.PARALLEL); + executionModes.add(ExecutionMode.SEQUENTIAL); + helper(new StreamAnalysisExpectedResult("new HashSet<>().stream()", executionModes, + Collections.singleton(Ordering.UNORDERED), false, true, false, null, null, null, + RefactoringStatus.ERROR, EnumSet.of(PreconditionFailure.INCONSISTENT_POSSIBLE_EXECUTION_MODES))); + } + public void testCollectionFromParameter() throws Exception { helper(new StreamAnalysisExpectedResult("h.parallelStream()", Collections.singleton(ExecutionMode.PARALLEL), Collections.singleton(Ordering.UNORDERED), false, true, false, null, null, null,