Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This prototype refactoring plug-in for [Eclipse](http://eclipse.org) represents

Explicit entry points may be marked using the appropriate annotation found in the corresponding [annotation library][annotations].

Explicit entry points can also be marked using importing entry points from a txt file. Each time we run the tool, a txt file named "entry_points.txt" is generated and contained in the current directory of workspace. Then, before next time we run tool to evaluate the same project, move or copy "entry_points.txt" into project directory or workspace directory of the project. While evaluating the project, the tool will ignore the explicit entry points which are added manually and regonize the explicit entry points through the file automatically.
Explicit entry points can also be marked using importing entry points from a txt file. Each time we run the tool, a txt file named "entry_points.txt" is generated and contained in the current directory of workspace. Then, before next time we run tool to evaluate the same project, move or copy "entry_points.txt" into project directory or workspace directory of the project. While evaluating the project, the tool will ignore the explicit entry points which are added manually and recognize the explicit entry points through the file automatically.

### Limitations

Expand All @@ -41,10 +41,17 @@ You should have the following projects in your workspace:

### Running the Evaluator

[annotations]: https://github.com/ponder-lab/edu.cuny.hunter.streamrefactoring.annotations
#### Configuring the Evaluation

A file named `eval.properties` can be placed at the project root. The following keys are available:

Key | Value Type | Description
---------------- | ---------- | ----------
nToUseForStreams | Integer | The value of N to use while building the nCFA for stream types.

### Further Information

See the [wiki][wiki] for further information.

[wiki]: https://github.com/ponder-lab/Java-8-Stream-Refactoring/wiki
[annotations]: https://github.com/ponder-lab/edu.cuny.hunter.streamrefactoring.annotations
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public enum PreconditionFailure {
CURRENTLY_NOT_HANDLED(14), // should just be #97 currently.
STREAM_CODE_NOT_REACHABLE(15), // either pivotal code isn't reachable or
// entry points are misconfigured.
NO_ENTRY_POINT(16); // user didn't specify entry points.
NO_ENTRY_POINT(16), // user didn't specify entry points.
NO_APPLICATION_CODE_IN_CALL_STRINGS(17); // N may be too small.

private int code;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.CancelException;

import edu.cuny.hunter.streamrefactoring.core.safe.NoApplicationCodeExistsInCallStringsException;
import edu.cuny.hunter.streamrefactoring.core.utils.LoggerNames;
import edu.cuny.hunter.streamrefactoring.core.utils.Util;
import edu.cuny.hunter.streamrefactoring.core.wala.EclipseProjectAnalysisEngine;
Expand Down Expand Up @@ -422,18 +423,33 @@ protected Ordering getInitialOrdering() {
}

public InstanceKey getInstanceKey(Collection<InstanceKey> trackedInstances,
EclipseProjectAnalysisEngine<InstanceKey> engine) throws InvalidClassFileException, IOException,
CoreException, InstanceKeyNotFoundException, UnhandledCaseException {
if (instanceKey == null) {
instanceKey = this.getInstructionForCreation(engine)
.flatMap(instruction -> trackedInstances.stream()
.filter(ik -> instanceKeyCorrespondsWithInstantiationInstruction(ik, instruction,
this.getEnclosingMethodReference(), engine.getCallGraph()))
.findFirst())
.orElseThrow(() -> new InstanceKeyNotFoundException("Can't find instance key for: "
+ this.getCreation() + " using tracked instances: " + trackedInstances));
EclipseProjectAnalysisEngine<InstanceKey> engine)
throws InvalidClassFileException, IOException, CoreException, InstanceKeyNotFoundException,
UnhandledCaseException, NoApplicationCodeExistsInCallStringsException {
// if not present.
if (this.instanceKey == null)
// compute it.
this.instanceKey = computeInstanceKey(trackedInstances, engine);
return this.instanceKey;
}

protected InstanceKey computeInstanceKey(Collection<InstanceKey> trackedInstances,
EclipseProjectAnalysisEngine<InstanceKey> engine)
throws InvalidClassFileException, IOException, CoreException, UnhandledCaseException,
NoApplicationCodeExistsInCallStringsException, InstanceKeyNotFoundException {
Optional<SSAInvokeInstruction> instructionForCreation = this.getInstructionForCreation(engine);

if (instructionForCreation.isPresent()) {
SSAInvokeInstruction instruction = instructionForCreation.get();

for (InstanceKey ik : trackedInstances)
if (instanceKeyCorrespondsWithInstantiationInstruction(ik, instruction,
this.getEnclosingMethodReference(), engine))
return ik;
}
return instanceKey;

throw new InstanceKeyNotFoundException(
"Can't find instance key for: " + this.getCreation() + " using tracked instances: " + trackedInstances);
}

Optional<SSAInvokeInstruction> getInstructionForCreation(EclipseProjectAnalysisEngine<InstanceKey> engine)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.BaseStream;
import java.util.stream.Collectors;

import org.eclipse.core.runtime.CoreException;
Expand Down Expand Up @@ -48,6 +49,8 @@ public class StreamAnalyzer extends ASTVisitor {

private static final Logger LOGGER = Logger.getLogger(LoggerNames.LOGGER_NAME);

private static final int N_FOR_STREAMS_DEFAULT = 2;

private static final String ENTRY_POINT_FILE = "entry_points.txt";

private static void addImplicitEntryPoints(Collection<Entrypoint> target, Iterable<Entrypoint> source) {
Expand All @@ -71,6 +74,11 @@ private static void addImplicitEntryPoints(Collection<Entrypoint> target, Iterab

private Set<Stream> streamSet = new HashSet<>();

/**
* The N to use for instances of {@link BaseStream} in the nCFA.
*/
private int nForStreams = N_FOR_STREAMS_DEFAULT;

public StreamAnalyzer() {
this(false);
}
Expand All @@ -79,24 +87,42 @@ public StreamAnalyzer(boolean visitDocTags) {
super(visitDocTags);
}

public StreamAnalyzer(boolean visitDocTags, int nForStreams) {
super(visitDocTags);
this.nForStreams = nForStreams;
}

public StreamAnalyzer(boolean visitDocTags, boolean findImplicitEntryPoints) {
this(visitDocTags);
this.findImplicitEntryPoints = findImplicitEntryPoints;
}

public StreamAnalyzer(boolean visitDocTags, int nForStreams, boolean findImplicitEntryPoints) {
this(visitDocTags, findImplicitEntryPoints);
this.nForStreams = nForStreams;
}

public StreamAnalyzer(boolean visitDocTags, boolean findImplicitEntryPoints, boolean findImplicitTestEntryPoints,
boolean findImplicitBenchmarkEntryPoints) {
this(visitDocTags, findImplicitEntryPoints);
this.findImplicitTestEntryPoints = findImplicitTestEntryPoints;
this.findImplicitBenchmarkEntryPoints = findImplicitBenchmarkEntryPoints;
}

public StreamAnalyzer(boolean visitDocTags, int nForStreams, boolean findImplicitEntryPoints,
boolean findImplicitTestEntryPoints, boolean findImplicitBenchmarkEntryPoints) {
this(visitDocTags, findImplicitEntryPoints, findImplicitTestEntryPoints, findImplicitBenchmarkEntryPoints);
this.nForStreams = nForStreams;
}

/**
* Analyzes this {@link StreamAnalyzer}'s streams.
*
* @return {@link Map} of project's analyzed along with the entry points used.
*/
public Map<IJavaProject, Collection<Entrypoint>> analyze() throws CoreException {
LOGGER.info(() -> "Using N = " + this.getNForStreams());

Map<IJavaProject, Collection<Entrypoint>> ret = new HashMap<>();

// collect the projects to be analyzed.
Expand All @@ -108,7 +134,7 @@ public Map<IJavaProject, Collection<Entrypoint>> analyze() throws CoreException
// create the analysis engine for the project.
EclipseProjectAnalysisEngine<InstanceKey> engine = null;
try {
engine = new EclipseProjectAnalysisEngine<>(project);
engine = new EclipseProjectAnalysisEngine<>(project, this.getNForStreams());
engine.buildAnalysisScope();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not create analysis engine for: " + project.getElementName(), e);
Expand Down Expand Up @@ -189,7 +215,8 @@ public Map<IJavaProject, Collection<Entrypoint>> analyze() throws CoreException
* graph.
* @return The {@link Entrypoint}s used in building the {@link CallGraph}.
*/
protected Collection<Entrypoint> buildCallGraph(EclipseProjectAnalysisEngine<InstanceKey> engine) throws IOException, CoreException, CallGraphBuilderCancelException, CancelException {
protected Collection<Entrypoint> buildCallGraph(EclipseProjectAnalysisEngine<InstanceKey> engine)
throws IOException, CoreException, CallGraphBuilderCancelException, CancelException {
// if we haven't built the call graph yet.
if (!this.enginesWithBuiltCallGraphsToEntrypointsUsed.keySet().contains(engine)) {

Expand Down Expand Up @@ -353,4 +380,12 @@ public boolean visit(MethodInvocation node) {

return super.visit(node);
}

public int getNForStreams() {
return nForStreams;
}

protected void setNForStreams(int nForStreams) {
this.nForStreams = nForStreams;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
import com.ibm.wala.util.strings.Atom;

import edu.cuny.hunter.streamrefactoring.core.safe.ModifiedBenignOracle;
import edu.cuny.hunter.streamrefactoring.core.safe.NoApplicationCodeExistsInCallStringsException;
import edu.cuny.hunter.streamrefactoring.core.safe.TypestateSolverFactory;
import edu.cuny.hunter.streamrefactoring.core.utils.LoggerNames;
import edu.cuny.hunter.streamrefactoring.core.wala.CallStringWithReceivers;
Expand Down Expand Up @@ -745,46 +746,26 @@ private void discoverPossibleSideEffects(EclipseProjectAnalysisEngine<InstanceKe
continue;

CallStringWithReceivers callString = Util.getCallString(instance);
CallSiteReference[] callSiteRefs = callString.getCallSiteRefs();
assert callSiteRefs.length == 2 : "Expecting call sites two-deep.";
assert callString.getMethods().length >= 1 : "Expecting call sites at least one-deep.";

// get the target of the caller.
MethodReference callerDeclaredTarget = callSiteRefs[1].getDeclaredTarget();

// get it's IR.
IMethod callerTargetMethod = engine.getClassHierarchy().resolveMethod(callerDeclaredTarget);
boolean fallback = false;

if (callerTargetMethod == null) {
LOGGER.warning("Cannot resolve caller declared target method: " + callerDeclaredTarget);

// fall back.
callerTargetMethod = callString.getMethods()[1];
LOGGER.warning("Falling back to method: " + callerTargetMethod);
fallback = true;
}

IR ir = engine.getCache().getIR(callerTargetMethod);
IR ir = engine.getCache().getIR(callString.getMethods()[0]);

if (ir == null) {
LOGGER.warning("Can't find IR for target: " + callerTargetMethod);
LOGGER.warning("Can't find IR for target: " + callString.getMethods()[0]);
continue; // next instance.
}

// get calls to the caller target.
// if we are falling back, use index 1, otherwise stick with index
// 0.
int callSiteRefsInx = fallback ? 1 : 0;
SSAAbstractInvokeInstruction[] calls = ir.getCalls(callSiteRefs[callSiteRefsInx]);
SSAAbstractInvokeInstruction[] calls = ir.getCalls(callString.getCallSiteRefs()[0]);
assert calls.length == 1 : "Are we only expecting one call here?";

// I guess we're only interested in ones with a single behavioral
// parameter (the first parameter is implicit).
if (calls[0].getNumberOfUses() == 2) {
// get the use of the first parameter.
int use = calls[0].getUse(1);
this.discoverLambdaSideEffects(engine, mod, Collections.singleton(instance), callerDeclaredTarget, ir,
use);
this.discoverLambdaSideEffects(engine, mod, Collections.singleton(instance),
callString.getMethods()[0].getReference(), ir, use);
}
}
}
Expand Down Expand Up @@ -866,18 +847,28 @@ private void fillInstanceToStreamMap(Set<Stream> streamSet, EclipseProjectAnalys
try {
instanceKey = stream.getInstanceKey(this.trackedInstances, engine);
} catch (InstanceKeyNotFoundException e) {
LOGGER.log(Level.WARNING, "Encountered unreachable code while processing: " + stream.getCreation(), e);
LOGGER.log(Level.WARNING,
"Encountered unreachable code while processing: " + stream.getCreation() + ".", e);
stream.addStatusEntry(PreconditionFailure.STREAM_CODE_NOT_REACHABLE,
"Either pivital code isn't reachable for stream: " + stream.getCreation()
+ " or entry points are misconfigured.");
++skippedStreams;
continue; // next stream.
} catch (UnhandledCaseException e) {
String msg = "Encountered possible unhandled case (AIC #155) while processing: " + stream.getCreation();
String msg = "Encountered possible unhandled case (AIC #155) while processing: " + stream.getCreation()
+ ".";
LOGGER.log(Level.WARNING, msg, e);
stream.addStatusEntry(PreconditionFailure.CURRENTLY_NOT_HANDLED, msg);
++skippedStreams;
continue; // next stream.
} catch (NoApplicationCodeExistsInCallStringsException e) {
LOGGER.log(Level.WARNING, "Did not encounter application code in call strings while processing: "
+ stream.getCreation() + ".", e);
stream.addStatusEntry(PreconditionFailure.NO_APPLICATION_CODE_IN_CALL_STRINGS,
"No application code in the call strings generated for stream: " + stream.getCreation()
+ " was found. The maximum call string length may need to be increased.");
++skippedStreams;
continue; // next stream.
}

// add the mapping.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import com.ibm.wala.ssa.SSAInvokeInstruction;
import com.ibm.wala.ssa.SSAPhiInstruction;
import com.ibm.wala.ssa.Value;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.TypeName;
import com.ibm.wala.types.TypeReference;
Expand Down Expand Up @@ -568,6 +569,7 @@ else if (Modifier.isAbstract(clazz.getModifiers()))
else
return false;
}


public static boolean isBaseStream(IClass clazz) {
return Util.isType(clazz, "java/util/stream", "BaseStream");
Expand Down Expand Up @@ -706,4 +708,24 @@ public static TypeReference getEvaluationType(IMethod method) {
// use the return type.
return method.getReturnType();
}
}

/**
* Returns the index of the first {@link IMethod} in methods that is client
* code.
*
* @param methods
* The {@link IMethod}s in question.
* @return The index of the first {@link IMethod} that is client code and -1 if
* none found.
*/
public static int findIndexOfFirstClientMethod(IMethod[] methods) {
for (int i = 0; i < methods.length; i++) {
IMethod meth = methods[i];

if (meth.getDeclaringClass().getClassLoader().getReference().equals(ClassLoaderReference.Application))
return i;
}

return -1; // not found.
}
}
Loading