diff --git a/checkstyle.xml b/checkstyle.xml
index 77a54d114..ab4bd739e 100644
--- a/checkstyle.xml
+++ b/checkstyle.xml
@@ -141,7 +141,6 @@
-
@@ -262,7 +261,7 @@
-
+
diff --git a/edu.cuny.hunter.hybridize.core/META-INF/MANIFEST.MF b/edu.cuny.hunter.hybridize.core/META-INF/MANIFEST.MF
index ed0ee3cfd..3cec2a06c 100644
--- a/edu.cuny.hunter.hybridize.core/META-INF/MANIFEST.MF
+++ b/edu.cuny.hunter.hybridize.core/META-INF/MANIFEST.MF
@@ -19,14 +19,18 @@ Require-Bundle: org.eclipse.ltk.core.refactoring;bundle-version="3.12.200",
org.eclipse.core.resources;bundle-version="3.18.0",
org.eclipse.core.runtime;bundle-version="3.26.0",
com.ibm.wala.ide;bundle-version="1.6.2"
-Import-Package: com.ibm.wala.cast.ipa.callgraph,
+Import-Package: com.google.common.collect,
+ com.ibm.wala.cast.ipa.callgraph,
com.ibm.wala.cast.ir.ssa,
com.ibm.wala.cast.loader,
com.ibm.wala.cast.python.client;version="0.1.0",
- com.ibm.wala.cast.python.ipa.callgraph,
+ com.ibm.wala.cast.python.ipa.callgraph;version="0.9.0",
com.ibm.wala.cast.python.loader;version="0.1.0",
com.ibm.wala.cast.python.ml.analysis;version="0.1.0",
com.ibm.wala.cast.python.ml.client;version="0.1.0",
+ com.ibm.wala.cast.python.modref;version="0.11.0",
+ com.ibm.wala.cast.python.ssa,
+ com.ibm.wala.cast.python.types,
com.ibm.wala.cast.python.util,
com.ibm.wala.cast.tree,
com.ibm.wala.classLoader,
diff --git a/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/analysis/Function.java b/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/analysis/Function.java
index afb092ca7..3994ff5ab 100644
--- a/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/analysis/Function.java
+++ b/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/analysis/Function.java
@@ -5,12 +5,20 @@
import static edu.cuny.hunter.hybridize.core.analysis.Refactoring.CONVERT_EAGER_FUNCTION_TO_HYBRID;
import static edu.cuny.hunter.hybridize.core.analysis.Refactoring.OPTIMIZE_HYBRID_FUNCTION;
import static edu.cuny.hunter.hybridize.core.analysis.Transformation.CONVERT_TO_EAGER;
+import static edu.cuny.hunter.hybridize.core.wala.ml.PythonModRefWithBuiltinFunctions.PythonModVisitorWithBuiltinFunctions.GLOBAL_OUTPUT_STREAM_POINTER_KEY;
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
import static org.eclipse.core.runtime.Platform.getLog;
import java.io.File;
+import java.util.Arrays;
import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.ILog;
@@ -19,6 +27,7 @@
import org.eclipse.jface.text.IDocument;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.RefactoringStatusContext;
+import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry;
import org.osgi.framework.FrameworkUtil;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.docutils.PySelection;
@@ -33,18 +42,34 @@
import org.python.pydev.parser.visitors.NodeUtils;
import org.python.pydev.parser.visitors.TypeInfo;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
+import com.ibm.wala.cast.ipa.callgraph.AstGlobalPointerKey;
import com.ibm.wala.cast.loader.AstMethod;
import com.ibm.wala.cast.python.ml.analysis.TensorTypeAnalysis;
import com.ibm.wala.cast.python.ml.analysis.TensorVariable;
+import com.ibm.wala.cast.python.types.PythonTypes;
import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position;
+import com.ibm.wala.cast.types.AstMethodReference;
import com.ibm.wala.classLoader.IMethod;
+import com.ibm.wala.classLoader.NewSiteReference;
import com.ibm.wala.ipa.callgraph.CGNode;
+import com.ibm.wala.ipa.callgraph.CallGraph;
+import com.ibm.wala.ipa.callgraph.propagation.InstanceFieldPointerKey;
+import com.ibm.wala.ipa.callgraph.propagation.InstanceKey;
import com.ibm.wala.ipa.callgraph.propagation.LocalPointerKey;
+import com.ibm.wala.ipa.callgraph.propagation.PointerAnalysis;
import com.ibm.wala.ipa.callgraph.propagation.PointerKey;
+import com.ibm.wala.ipa.callgraph.propagation.StaticFieldKey;
+import com.ibm.wala.ipa.modref.ModRef;
+import com.ibm.wala.types.MethodReference;
+import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.Pair;
+import com.ibm.wala.util.intset.OrdinalSet;
-import edu.cuny.citytech.refactoring.common.core.RefactorableProgramEntity;
import edu.cuny.hunter.hybridize.core.utils.RefactoringAvailabilityTester;
+import edu.cuny.hunter.hybridize.core.wala.ml.PythonModRefWithBuiltinFunctions;
/**
* A representation of a Python function.
@@ -52,7 +77,16 @@
* @author Raffi Khatchadourian
* @author Tatiana Castro VĂ©lez
*/
-public class Function extends RefactorableProgramEntity {
+public class Function {
+
+ public static final String PLUGIN_ID = FrameworkUtil.getBundle(Function.class).getSymbolicName();
+
+ private final class FunctionStatusContext extends RefactoringStatusContext {
+ @Override
+ public Object getCorrespondingElement() {
+ return Function.this;
+ }
+ }
/**
* Parameters that may be passed to a tf.fuction decorator. Parameter descriptions found at:
@@ -272,6 +306,11 @@ public boolean getReduceRetracingParamExists() {
private static final ILog LOG = getLog(Function.class);
+ /**
+ * True iff verbose output is desired.
+ */
+ private static final boolean VERBOSE = false;
+
/**
* This {@link Function}'s associated hybridization parameters.
*/
@@ -292,6 +331,11 @@ public boolean getReduceRetracingParamExists() {
*/
private Boolean likelyHasTensorParameter;
+ /**
+ * True iff this {@link Function} has Python side-effects.
+ */
+ private Boolean hasPythonSideEffects;
+
/**
* TODO: Populate.
*/
@@ -312,10 +356,251 @@ public boolean getReduceRetracingParamExists() {
private RefactoringStatus status = new RefactoringStatus();
+ private static Map>> creationsCache = Maps.newHashMap();
+
public Function(FunctionDefinition fd) {
this.functionDefinition = fd;
}
+ /**
+ * Infer Python side-effects potentially produced by executing this {@link Function}.
+ *
+ * @param callGraph The system {@link CallGraph}.
+ * @param pointerAnalysis The system {@link PointerAnalysis}.
+ * @throws UndeterminablePythonSideEffectsException If this {@link Function}'s representation isn't found in the given
+ * {@link CallGraph}.
+ */
+ public void inferPythonSideEffects(CallGraph callGraph, PointerAnalysis pointerAnalysis)
+ throws UndeterminablePythonSideEffectsException {
+ ModRef modRef = new PythonModRefWithBuiltinFunctions();
+ Map> mod = modRef.computeMod(callGraph, pointerAnalysis);
+
+ // Get the nodes corresponding to this function's declaration. NOTE: There can be multiple nodes for a function declaration under
+ // the current representation. It seems that there is a declaration node for each call to the function. Each node has a different
+ // calling context.
+ Set nodes = this.getNodes(callGraph);
+
+ if (nodes.isEmpty())
+ throw new UndeterminablePythonSideEffectsException(this.getMethodReference());
+
+ // Only consider the first node. The calling context shouldn't matter for us right now.
+ CGNode cgNode = nodes.iterator().next();
+
+ // Get the locations (pointers) modified by this function.
+ OrdinalSet modSet = mod.get(cgNode);
+ LOG.info("Found " + modSet.size() + " original modified location(s).");
+ modSet.forEach(pk -> LOG.info("Original modified location: " + pk + "."));
+
+ // Filter out the modified locations.
+ Set filteredModSet = this.filterSideEffects(modSet, callGraph, pointerAnalysis);
+ LOG.info("Found " + filteredModSet.size() + " filtered modified location(s).");
+ filteredModSet.forEach(pk -> LOG.info("Filtered modified location: " + pk + "."));
+
+ // Log the locations we are removing.
+ SetView removed = Sets.difference(Sets.newHashSet(modSet), filteredModSet);
+ LOG.info("Removed " + removed.size() + " locations.");
+ removed.forEach(pk -> LOG.info("Removed modified location: " + pk + "."));
+
+ if (!filteredModSet.isEmpty()) {
+ this.setHasPythonSideEffects(TRUE);
+ LOG.info(this + " has side-effects.");
+ return;
+ }
+
+ this.setHasPythonSideEffects(FALSE);
+ LOG.info(this + " does not have side-effects.");
+ }
+
+ private Set filterSideEffects(Iterable modSet, CallGraph callGraph,
+ PointerAnalysis pointerAnalysis) {
+ Set ret = new HashSet<>();
+
+ for (PointerKey pointerKey : modSet) {
+ if (pointerKey instanceof InstanceFieldPointerKey) {
+ InstanceFieldPointerKey fieldPointerKey = (InstanceFieldPointerKey) pointerKey;
+ InstanceKey instanceKey = fieldPointerKey.getInstanceKey();
+
+ if (allCreationsWithinClosure(this.getMethodReference(), instanceKey, callGraph))
+ continue; // filter this pointer out.
+
+ ret.add(fieldPointerKey);
+ } else if (pointerKey instanceof LocalPointerKey || pointerKey instanceof StaticFieldKey) {
+ OrdinalSet pointsToSet = pointerAnalysis.getPointsToSet(pointerKey);
+
+ boolean skipPointerKey = true;
+
+ for (InstanceKey ik : pointsToSet)
+ skipPointerKey &= allCreationsWithinClosure(this.getMethodReference(), ik, callGraph);
+
+ if (skipPointerKey && !pointsToSet.isEmpty())
+ continue; // filter this pointer out.
+
+ ret.add(pointerKey);
+ } else if (pointerKey instanceof AstGlobalPointerKey) {
+ AstGlobalPointerKey globalPointerKey = (AstGlobalPointerKey) pointerKey;
+
+ if (globalPointerKey.equals(GLOBAL_OUTPUT_STREAM_POINTER_KEY))
+ ret.add(globalPointerKey);
+ else
+ throw new IllegalArgumentException("Not expecting global pointer key: " + globalPointerKey + ".");
+ } else
+ throw new IllegalArgumentException("Not expecting pointer key: " + pointerKey + " of type: " + pointerKey.getClass() + ".");
+ }
+
+ return ret;
+ }
+
+ private static boolean allCreationsWithinClosure(MethodReference methodReference, InstanceKey instanceKey, CallGraph callGraph) {
+ Set seen = Sets.newHashSet();
+ return allCreationsWithinClosureInteral(methodReference, instanceKey, callGraph, seen);
+
+ }
+
+ private static boolean allCreationsWithinClosureInteral(MethodReference methodReference, InstanceKey instanceKey, CallGraph callGraph,
+ Set seen) {
+ Map> cache2 = creationsCache.get(methodReference);
+
+ if (cache2 != null) {
+ Map cache3 = cache2.get(instanceKey);
+
+ if (cache3 != null) {
+ Boolean result = cache3.get(callGraph);
+
+ if (result != null)
+ return result;
+ }
+ }
+
+ boolean result = allCreationsWithinClosureInteral2(methodReference, instanceKey, callGraph, seen);
+
+ if (cache2 == null) {
+ cache2 = Maps.newHashMap();
+ creationsCache.put(methodReference, cache2);
+ }
+
+ Map cache3 = cache2.get(instanceKey);
+
+ if (cache3 == null) {
+ cache3 = Maps.newHashMap();
+ cache2.put(instanceKey, cache3);
+ }
+
+ Boolean previous = cache3.put(callGraph, result);
+ assert previous == null : "Should be a new key.";
+
+ return result;
+ }
+
+ private static boolean allCreationsWithinClosureInteral2(MethodReference methodReference, InstanceKey instanceKey, CallGraph callGraph,
+ Set seen) {
+ seen.add(methodReference);
+
+ // check this function.
+ if (allCreationsWithin(methodReference, instanceKey, callGraph))
+ return true;
+
+ // otherwise, check its callees.
+ Set cgNodes = getNodes(methodReference, callGraph);
+
+ if (cgNodes.isEmpty())
+ throw new IllegalArgumentException("Can't find call graph nodes corresponding to: " + methodReference + ".");
+
+ // Only consider the first node. The only difference should be the calling context, which shouldn't make a difference for us.
+ CGNode node = cgNodes.iterator().next();
+
+ // check the callees.
+ for (Iterator succNodes = callGraph.getSuccNodes(node); succNodes.hasNext();) {
+ CGNode next = succNodes.next();
+ MethodReference reference = next.getMethod().getReference();
+
+ if (!seen.contains(reference)) {
+ seen.add(reference);
+
+ if (allCreationsWithinClosureInteral(reference, instanceKey, callGraph, seen))
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean allCreationsWithin(MethodReference methodReference, InstanceKey instanceKey, CallGraph callGraph) {
+ int numCreations = 0;
+
+ // for each creation site of the given instance.
+ for (Iterator> it = instanceKey.getCreationSites(callGraph); it.hasNext();) {
+ Pair creationSite = it.next();
+ CGNode creationNode = creationSite.fst;
+ NewSiteReference newSiteReference = creationSite.snd;
+
+ // is this instance being created outside this function?
+ if (!(creationNode.getMethod().getReference().equals(methodReference)
+ || newSiteReference.getDeclaredType().equals(methodReference.getDeclaringClass())))
+ return false;
+
+ ++numCreations;
+ }
+
+ if (numCreations == 0) // if there are no creations.
+ // then, they can't be within this method.
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Get the {@link CallGraph} nodes corresponding to this {@link Function}.
+ *
+ * @param callGraph The {@link CallGraph} to search.
+ * @return The nodes in the {@link CallGraph} corresponding to this {@link Function}.
+ * @apiNote There can be multiple nodes for a single {@link Function} under the current representation.
+ */
+ private Set getNodes(CallGraph callGraph) {
+ return getNodes(this.getMethodReference(), callGraph);
+ }
+
+ /**
+ * Get the {@link CallGraph} nodes corresponding to the given {@link MethodReference}.
+ *
+ * @param methodReference The method to search for.
+ * @param callGraph The {@link CallGraph} to search.
+ * @return The nodes in the {@link CallGraph} corresponding to this {@link Function}.
+ * @apiNote There can be multiple nodes for a single {@link Function} under the current representation.
+ */
+ private static Set getNodes(MethodReference methodReference, CallGraph callGraph) {
+ Set nodes = callGraph.getNodes(methodReference);
+
+ if (nodes.isEmpty()) {
+ LOG.error("Can't get call graph nodes for: " + methodReference + ".");
+
+ if (VERBOSE) {
+ LOG.info("Method reference is: " + methodReference + ".");
+ LOG.info("Call graph nodes:\n" + callGraph.stream().map(Objects::toString).collect(Collectors.joining("\n")));
+ }
+ }
+
+ LOG.info("Found " + nodes.size() + " node(s) corresponding to: " + methodReference + ".");
+
+ if (VERBOSE)
+ LOG.info("Nodes:\n" + nodes.stream().map(Objects::toString).collect(Collectors.joining("\n")));
+
+ return nodes;
+ }
+
+ public MethodReference getMethodReference() {
+ TypeReference typeReference = getDeclaringClass();
+ return MethodReference.findOrCreate(typeReference, AstMethodReference.fnSelector);
+ }
+
+ public TypeReference getDeclaringClass() {
+ File containingFile = this.getContainingFile();
+ String filename = containingFile.getName();
+ String modifiedIdentifier = this.getIdentifier().replace('.', '/');
+ String typeName = "Lscript " + filename + "/" + modifiedIdentifier;
+
+ return TypeReference.findOrCreate(PythonTypes.pythonLoader, typeName);
+ }
+
public void inferTensorTensorParameters(TensorTypeAnalysis analysis, IProgressMonitor monitor) throws BadLocationException {
monitor.beginTask("Analyzing whether function has a tensor parameter.", IProgressMonitor.UNKNOWN);
// TODO: What if there are no current calls to the function? How will we determine its type?
@@ -580,48 +865,70 @@ private static boolean isHybrid(decoratorsType decorator, String containingModul
return false;
}
- private void addStatusEntry(PreconditionFailure failure, String message) {
- RefactoringStatusContext context = new RefactoringStatusContext() {
+ public void addFailure(PreconditionFailure failure, String message) {
+ // If is side-effects is filled, we can't set a precondition failure that we can't determine them.
+ assert this.getHasPythonSideEffects() == null
+ || failure != PreconditionFailure.UNDETERMINABLE_SIDE_EFFECTS : "Can't both have side-effects filled and have tem undterminable.";
- @Override
- public Object getCorrespondingElement() {
- return Function.this.getFunctionDefinition().getFunctionDef();
- }
- };
+ RefactoringStatusContext context = new FunctionStatusContext();
+ this.getStatus().addEntry(RefactoringStatus.ERROR, message, context, PLUGIN_ID, failure.getCode(), this);
+ }
- this.getStatus().addEntry(RefactoringStatus.ERROR, message, context, FrameworkUtil.getBundle(Function.class).getSymbolicName(),
- failure.getCode(), this);
+ public void addWarning(String message) {
+ RefactoringStatusContext context = new FunctionStatusContext();
+ this.getStatus().addEntry(RefactoringStatus.WARNING, message, context, PLUGIN_ID, RefactoringStatusEntry.NO_CODE, this);
}
/**
* Check refactoring preconditions.
*/
public void check() {
- // we can't refactor it if either it doesn't have a tensor parameter or it's not currently hybrid.
- if (!(this.getLikelyHasTensorParameter() || this.isHybrid()))
- this.addStatusEntry(PreconditionFailure.OPTIMIZATION_NOT_AVAILABLE,
- "This function is not available for optimization. Either the function must have a tensor-like parameter or be currently hybrid.");
-
- // if this is not a hybrid function.
- if (!this.isHybrid()) {
- // but it likely has a tensor parameter.
+ if (!this.isHybrid()) { // Eager. Table 1.
+ this.setRefactoring(CONVERT_EAGER_FUNCTION_TO_HYBRID);
+
if (this.getLikelyHasTensorParameter()) {
- // hybridize it.
- this.setRefactoring(CONVERT_EAGER_FUNCTION_TO_HYBRID);
- this.addTransformation(Transformation.CONVERT_TO_HYBRID);
- this.setPassingPrecondition(P1);
+ if (this.getHasPythonSideEffects() != null && !this.getHasPythonSideEffects()) {
+ this.addTransformation(Transformation.CONVERT_TO_HYBRID);
+ this.setPassingPrecondition(P1);
+ } else if (this.getHasPythonSideEffects() != null) // it has side-effects.
+ this.addFailure(PreconditionFailure.HAS_PYTHON_SIDE_EFFECTS, "Can't hybridize a function with Python side-effects.");
+ } else { // no tensor parameters.
+ this.addFailure(PreconditionFailure.HAS_NO_TENSOR_PARAMETERS,
+ "This function has no tensor parameters and may not benefit from hybridization.");
+
+ if (this.getHasPythonSideEffects() != null && this.getHasPythonSideEffects())
+ this.addFailure(PreconditionFailure.HAS_PYTHON_SIDE_EFFECTS, "Can't hybridize a function with Python side-effects.");
}
- } else { // this is a hybrid function.
- // but it does not likely have a tensor parameter.
+ } else { // Hybrid. Use table 2.
+ this.setRefactoring(OPTIMIZE_HYBRID_FUNCTION);
+
if (!this.getLikelyHasTensorParameter()) {
- // de-hybridize it.
- this.setRefactoring(OPTIMIZE_HYBRID_FUNCTION);
- this.addTransformation(CONVERT_TO_EAGER);
- this.setPassingPrecondition(P2);
+ if (this.getHasPythonSideEffects() != null && !this.getHasPythonSideEffects()) {
+ this.addTransformation(CONVERT_TO_EAGER);
+ this.setPassingPrecondition(P2);
+ } else if (this.getHasPythonSideEffects() != null) // it has side-effects.
+ this.addFailure(PreconditionFailure.HAS_PYTHON_SIDE_EFFECTS,
+ "De-hybridizing a function with Python side-effects may alter semantics.");
+ } else { // it has a tensor parameter.
+ this.addFailure(PreconditionFailure.HAS_TENSOR_PARAMETERS,
+ "Functions with tensor parameters may benefit from hybreidization.");
+
+ if (this.getHasPythonSideEffects() != null && this.getHasPythonSideEffects()) {
+ this.addFailure(PreconditionFailure.HAS_PYTHON_SIDE_EFFECTS,
+ "De-hybridizing a function with Python side-effects may alter semantics.");
+ }
}
+
+ // Warn if the function has side-effects.
+ if (this.getHasPythonSideEffects() != null && this.getHasPythonSideEffects())
+ this.addWarning("This hybrid function potentially contains Python side-effects.");
}
}
+ public boolean willDehybridize() {
+ return this.getTransformations().contains(CONVERT_TO_EAGER);
+ }
+
public IDocument getContainingDocument() {
return this.getFunctionDefinition().containingDocument;
}
@@ -771,4 +1078,53 @@ public Refactoring getRefactoring() {
public void setRefactoring(Refactoring refactoring) {
this.refactoring = refactoring;
}
+
+ public Boolean getHasPythonSideEffects() {
+ return this.hasPythonSideEffects;
+ }
+
+ protected void setHasPythonSideEffects(Boolean hasPythonSideEffects) {
+ assert this.hasPythonSideEffects == null : "Can only set side-effects once.";
+ assert hasPythonSideEffects == null || this.getStatus().getEntryMatchingCode(PLUGIN_ID,
+ PreconditionFailure.UNDETERMINABLE_SIDE_EFFECTS.getCode()) == null : "Can't set side-effects if they are undeterminable.";
+
+ this.hasPythonSideEffects = hasPythonSideEffects;
+ }
+
+ /**
+ * Returns true iff there is at most one {@link RefactoringStatusEntry} for a particular kind of failure.
+ *
+ * @apiNote This is to prevent counting a single kind of failure multiple times. Though that may be valid, I don't believe we have a
+ * situation like this currently.
+ * @return True iff there is at most one failure per failure kind.
+ */
+ public boolean hasOnlyOneFailurePerKind() {
+ Map> failureCodeToEntries = Arrays.stream(this.getStatus().getEntries())
+ .collect(Collectors.groupingBy(RefactoringStatusEntry::getCode));
+
+ for (Integer code : failureCodeToEntries.keySet()) {
+ List failuresForCode = failureCodeToEntries.get(code);
+ if (failuresForCode.size() > 1)
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the first {@link RefactoringStatusEntry} matching the given {@link PreconditionFailure}'s code in this {@link Function}'s
+ * {@link RefactoringStatus}.
+ *
+ * @param failure The {@link PreconditionFailure} whose {@link RefactoringStatusEntry} to find.
+ * @return The first {@link RefactoringStatusEntry} matching the given {@link PreconditionFailure}'s code in this {@link Function}'s
+ * {@link RefactoringStatus}.
+ */
+ public RefactoringStatusEntry getEntryMatchingFailure(PreconditionFailure failure) {
+ return this.getStatus().getEntryMatchingCode(Function.PLUGIN_ID, failure.getCode());
+ }
+
+ public static void clearCaches() {
+ creationsCache.clear();
+ }
+
}
diff --git a/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/analysis/PreconditionFailure.java b/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/analysis/PreconditionFailure.java
index 2fe5424cc..cfe6eb72c 100644
--- a/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/analysis/PreconditionFailure.java
+++ b/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/analysis/PreconditionFailure.java
@@ -3,7 +3,18 @@
import java.util.Arrays;
public enum PreconditionFailure {
- CURRENTLY_NOT_HANDLED(1), OPTIMIZATION_NOT_AVAILABLE(2);
+ CURRENTLY_NOT_HANDLED(1),
+
+ /**
+ * Either there is no call to the function, there is a call but don't handle it, or something about decorators?.
+ */
+ UNDETERMINABLE_SIDE_EFFECTS(3),
+
+ HAS_PYTHON_SIDE_EFFECTS(4),
+
+ HAS_NO_TENSOR_PARAMETERS(6),
+
+ HAS_TENSOR_PARAMETERS(7);
static {
// check that the codes are unique.
diff --git a/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/analysis/UndeterminablePythonSideEffectsException.java b/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/analysis/UndeterminablePythonSideEffectsException.java
new file mode 100644
index 000000000..8f247c60b
--- /dev/null
+++ b/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/analysis/UndeterminablePythonSideEffectsException.java
@@ -0,0 +1,12 @@
+package edu.cuny.hunter.hybridize.core.analysis;
+
+import com.ibm.wala.types.MethodReference;
+
+public class UndeterminablePythonSideEffectsException extends Exception {
+
+ private static final long serialVersionUID = -1229657254725226075L;
+
+ public UndeterminablePythonSideEffectsException(MethodReference methodReference) {
+ super("Can't find: " + methodReference + " in call graph.");
+ }
+}
diff --git a/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/refactorings/HybridizeFunctionRefactoringProcessor.java b/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/refactorings/HybridizeFunctionRefactoringProcessor.java
index d04e676b2..5d1cbb6be 100644
--- a/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/refactorings/HybridizeFunctionRefactoringProcessor.java
+++ b/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/refactorings/HybridizeFunctionRefactoringProcessor.java
@@ -1,5 +1,6 @@
package edu.cuny.hunter.hybridize.core.refactorings;
+import static java.lang.Boolean.TRUE;
import static org.eclipse.core.runtime.Platform.getLog;
import java.io.IOException;
@@ -8,6 +9,7 @@
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
@@ -20,10 +22,11 @@
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.NullChange;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.RefactoringStatusContext;
import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant;
import org.eclipse.ltk.core.refactoring.participants.SharableParticipants;
-import org.python.pydev.ast.refactoring.TooManyMatchesException;
+import org.python.pydev.ast.refactoring.TooManyMatchesException; /* FIXME: This exception sounds too low-level. */
import org.python.pydev.core.preferences.InterpreterGeneralPreferences;
import com.ibm.wala.cast.ipa.callgraph.CAstCallGraphUtil;
@@ -39,6 +42,8 @@
import edu.cuny.citytech.refactoring.common.core.TimeCollector;
import edu.cuny.hunter.hybridize.core.analysis.Function;
import edu.cuny.hunter.hybridize.core.analysis.FunctionDefinition;
+import edu.cuny.hunter.hybridize.core.analysis.PreconditionFailure;
+import edu.cuny.hunter.hybridize.core.analysis.UndeterminablePythonSideEffectsException;
import edu.cuny.hunter.hybridize.core.descriptors.HybridizeFunctionRefactoringDescriptor;
import edu.cuny.hunter.hybridize.core.messages.Messages;
import edu.cuny.hunter.hybridize.core.wala.ml.EclipsePythonProjectTensorAnalysisEngine;
@@ -46,6 +51,21 @@
@SuppressWarnings("unused")
public class HybridizeFunctionRefactoringProcessor extends RefactoringProcessor {
+ private static final String DUMP_CALL_GRAPH_PROPERTY_KEY = "edu.cuny.hunter.hybridize.dumpCallGraph";
+
+ private final class FunctionStatusContext extends RefactoringStatusContext {
+ private final Function func;
+
+ private FunctionStatusContext(Function func) {
+ this.func = func;
+ }
+
+ @Override
+ public Object getCorrespondingElement() {
+ return func;
+ }
+ }
+
private static final ILog LOG = getLog(HybridizeFunctionRefactoringProcessor.class);
private static RefactoringStatus checkDecorators(Function func) {
@@ -77,17 +97,32 @@ private static RefactoringStatus checkParameters(Function func) {
/**
* True iff the {@link CallGraph} should be displayed.
*/
- private boolean dumpCallGraph;
+ private boolean dumpCallGraph = Boolean.getBoolean(DUMP_CALL_GRAPH_PROPERTY_KEY);
+
+ private boolean alwaysCheckPythonSideEffects;
+
+ private boolean processFunctionsInParallel = true;
public HybridizeFunctionRefactoringProcessor() {
// Force the use of typeshed. It's an experimental feature of PyDev.
- InterpreterGeneralPreferences.FORCE_USE_TYPESHED = Boolean.TRUE;
+ InterpreterGeneralPreferences.FORCE_USE_TYPESHED = TRUE;
// Have WALA dump the call graph if appropriate.
CAstCallGraphUtil.AVOID_DUMP = !this.dumpCallGraph;
}
- public HybridizeFunctionRefactoringProcessor(Set functionDefinitionSet) throws TooManyMatchesException {
+ public HybridizeFunctionRefactoringProcessor(boolean alwaysCheckPythonSideEffects) {
+ this();
+ this.alwaysCheckPythonSideEffects = alwaysCheckPythonSideEffects;
+ }
+
+ public HybridizeFunctionRefactoringProcessor(boolean alwaysCheckPythonSideEffects, boolean processFunctionsInParallel) {
+ this(alwaysCheckPythonSideEffects);
+ this.processFunctionsInParallel = processFunctionsInParallel;
+ }
+
+ public HybridizeFunctionRefactoringProcessor(Set functionDefinitionSet)
+ throws TooManyMatchesException /* FIXME: This exception sounds too low-level. */ {
this();
// Convert the FunctionDefs to Functions.
@@ -103,6 +138,18 @@ public HybridizeFunctionRefactoringProcessor(Set functionDef
}
}
+ public HybridizeFunctionRefactoringProcessor(Set functionDefinitionSet, boolean alwaysCheckPythonSideEffects)
+ throws TooManyMatchesException /* FIXME: This exception sounds too low-level. */ {
+ this(functionDefinitionSet);
+ this.alwaysCheckPythonSideEffects = alwaysCheckPythonSideEffects;
+ }
+
+ public HybridizeFunctionRefactoringProcessor(Set functionDefinitionSet, boolean alwaysCheckPythonSideEffects,
+ boolean processFunctionsInParallel) throws TooManyMatchesException /* FIXME: This exception sounds too low-level. */ {
+ this(functionDefinitionSet, alwaysCheckPythonSideEffects);
+ this.processFunctionsInParallel = processFunctionsInParallel;
+ }
+
@Override
public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext context)
throws CoreException, OperationCanceledException {
@@ -176,27 +223,38 @@ private RefactoringStatus checkFunctions(IProgressMonitor monitor) throws Operat
LOG.info("Checking " + projectFunctions.size() + " function" + (allFunctions.size() > 1 ? "s" : "") + ".");
subMonitor.beginTask(Messages.CheckingFunctions, allFunctions.size());
- for (Function func : projectFunctions) {
+ this.getStream(projectFunctions).forEach(func -> {
LOG.info("Checking function: " + func + ".");
// Find out if it's hybrid via the tf.function decorator.
try {
- func.computeHybridization(monitor);
+ func.computeHybridization(subMonitor.split(IProgressMonitor.UNKNOWN));
} catch (BadLocationException e) {
- throw new CoreException(Status.error("Could not compute hybridization for: : " + func, e));
+ throw new RuntimeException("Could not compute hybridization for: " + func + ".", e);
}
- // TODO: Whether a function has a tensor argument should probably be an initial
- // condition: functions w/o such arguments should not be candidates.
try {
- func.inferTensorTensorParameters(analysis, monitor);
+ func.inferTensorTensorParameters(analysis, subMonitor.split(IProgressMonitor.UNKNOWN));
} catch (BadLocationException e) {
- throw new CoreException(Status.error("Could not infer tensor parameters for: : " + func, e));
+ throw new RuntimeException("Could not infer tensor parameters for: " + func + ".", e);
+ }
+
+ // Check Python side-effects.
+ try {
+ if (this.getAlwaysCheckPythonSideEffects() || func.isHybrid() || func.getLikelyHasTensorParameter())
+ func.inferPythonSideEffects(callGraph, builder.getPointerAnalysis());
+ } catch (UndeterminablePythonSideEffectsException e) {
+ LOG.warn("Unable to infer side-effects of: " + func + ".", e);
+ func.addFailure(PreconditionFailure.UNDETERMINABLE_SIDE_EFFECTS,
+ "Can't infer side-effects, most likely due to a call graph issue caused by a decorator or a missing function call.");
+ // next function.
+ status.merge(func.getStatus());
+ subMonitor.worked(1);
+ return;
}
// check the function preconditions.
func.check();
- status.merge(func.getStatus());
status.merge(checkParameters(func));
subMonitor.checkCanceled();
@@ -204,13 +262,28 @@ private RefactoringStatus checkFunctions(IProgressMonitor monitor) throws Operat
status.merge(checkDecorators(func));
subMonitor.checkCanceled();
+ status.merge(func.getStatus());
subMonitor.worked(1);
- }
+
+ assert func.hasOnlyOneFailurePerKind() : "Count failures only once.";
+ });
}
return status;
}
+ /**
+ * Returns a {@link Stream} of {@link Function}s. Properties of the stream are dependent on the state of this
+ * {@link HybridizeFunctionRefactoringProcessor}.
+ *
+ * @param functions The {@link Set} of {@link Function}s from which to derive a {@link Stream}.
+ * @return A potentially parallel {@link Stream} of {@link Function}s.
+ */
+ private Stream getStream(Set functions) {
+ Stream stream = functions.stream();
+ return this.getProcessFunctionsInParallel() ? stream.parallel() : stream;
+ }
+
private TensorTypeAnalysis computeTensorTypeAnalysis(EclipsePythonProjectTensorAnalysisEngine engine,
PythonSSAPropagationCallGraphBuilder builder) throws CancelException {
Map projectToTensorTypeAnalysis = this.getProjectToTensorTypeAnalysis();
@@ -261,6 +334,7 @@ protected void clearCaches() {
this.getProjectToCallGraphBuilder().clear();
this.getProjectToCallGraph().clear();
this.getProjectToTensorTypeAnalysis().clear();
+ Function.clearCaches();
}
@Override
@@ -293,6 +367,10 @@ public String getProcessorName() {
return Messages.Name;
}
+ private boolean getAlwaysCheckPythonSideEffects() {
+ return this.alwaysCheckPythonSideEffects;
+ }
+
@Override
public boolean isApplicable() throws CoreException {
// TODO Auto-generated method stub
@@ -321,4 +399,13 @@ protected boolean getDumpCallGraph() {
public Map getProjectToTensorTypeAnalysis() {
return projectToTensorTypeAnalysis;
}
+
+ /**
+ * True iff project functions should be processed in parallel. Otherwise, they are processed sequentially.
+ *
+ * @return True iff project functions should be processed in parallel.
+ */
+ private boolean getProcessFunctionsInParallel() {
+ return this.processFunctionsInParallel;
+ }
}
diff --git a/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/utils/Util.java b/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/utils/Util.java
index 1e333ea5d..39231bbe6 100644
--- a/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/utils/Util.java
+++ b/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/utils/Util.java
@@ -48,10 +48,10 @@ public static Refactoring createRefactoring(Set functionDefi
return new ProcessorBasedRefactoring(new HybridizeFunctionRefactoringProcessor(functionDefinitions));
}
- public static HybridizeFunctionRefactoringProcessor createHybridizeFunctionRefactoring(IProject[] projects)
+ public static HybridizeFunctionRefactoringProcessor createHybridizeFunctionRefactoring(IProject[] projects, boolean alwaysCheckPythonSideEffects)
throws ExecutionException, CoreException, IOException {
Set functionDefinitions = getFunctionDefinitions(Arrays.asList(projects));
- return new HybridizeFunctionRefactoringProcessor(functionDefinitions);
+ return new HybridizeFunctionRefactoringProcessor(functionDefinitions, alwaysCheckPythonSideEffects);
}
public static Set getFunctionDefinitions(Iterable> iterable)
diff --git a/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/wala/ml/EclipsePythonProjectTensorAnalysisEngine.java b/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/wala/ml/EclipsePythonProjectTensorAnalysisEngine.java
index 89a4557c4..d21e8159c 100644
--- a/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/wala/ml/EclipsePythonProjectTensorAnalysisEngine.java
+++ b/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/wala/ml/EclipsePythonProjectTensorAnalysisEngine.java
@@ -52,13 +52,13 @@ public EclipsePythonProjectTensorAnalysisEngine(IProject project) {
this.project = project;
IPath projectPath = getPath(project);
Module dirModule = new EclipseSourceDirectoryTreeModule(projectPath, null, ".py");
- LOG.info("Creating engine from: " + dirModule);
+ LOG.info("Creating engine from: " + dirModule + ".");
this.setModuleFiles(Collections.singleton(dirModule));
for (Iterator extends ModuleEntry> entries = dirModule.getEntries(); entries.hasNext();) {
ModuleEntry entry = entries.next();
- LOG.info("Found entry: " + entry);
+ LOG.info("Found entry: " + entry + ".");
}
}
diff --git a/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/wala/ml/PythonModRefWithBuiltinFunctions.java b/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/wala/ml/PythonModRefWithBuiltinFunctions.java
new file mode 100644
index 000000000..1d7074c8b
--- /dev/null
+++ b/edu.cuny.hunter.hybridize.core/src/edu/cuny/hunter/hybridize/core/wala/ml/PythonModRefWithBuiltinFunctions.java
@@ -0,0 +1,107 @@
+package edu.cuny.hunter.hybridize.core.wala.ml;
+
+import static com.ibm.wala.cast.python.types.PythonTypes.list;
+import static com.ibm.wala.cast.python.types.PythonTypes.string;
+
+import java.util.Collection;
+
+import com.ibm.wala.cast.ipa.callgraph.AstGlobalPointerKey;
+import com.ibm.wala.cast.ir.ssa.AstLexicalAccess.Access;
+import com.ibm.wala.cast.ir.ssa.AstLexicalRead;
+import com.ibm.wala.cast.python.modref.PythonModRef;
+import com.ibm.wala.cast.python.ssa.PythonInvokeInstruction;
+import com.ibm.wala.cast.python.ssa.PythonPropertyRead;
+import com.ibm.wala.cast.python.types.PythonTypes;
+import com.ibm.wala.ipa.callgraph.CGNode;
+import com.ibm.wala.ipa.callgraph.propagation.ConstantKey;
+import com.ibm.wala.ipa.callgraph.propagation.InstanceKey;
+import com.ibm.wala.ipa.callgraph.propagation.PointerAnalysis;
+import com.ibm.wala.ipa.callgraph.propagation.PointerKey;
+import com.ibm.wala.ipa.modref.ExtendedHeapModel;
+import com.ibm.wala.ssa.SSAInstruction;
+import com.ibm.wala.types.TypeReference;
+import com.ibm.wala.util.intset.OrdinalSet;
+
+public class PythonModRefWithBuiltinFunctions extends PythonModRef {
+
+ public static class PythonModVisitorWithBuiltinFunctions extends PythonModVisitor {
+
+ public static final AstGlobalPointerKey GLOBAL_OUTPUT_STREAM_POINTER_KEY = new AstGlobalPointerKey(
+ PythonModVisitorWithBuiltinFunctions.class.getPackageName().replace('.', '/') + "/OUT");
+
+ private static final String PRINT_FUNCTION_VARIABLE_NAME = "print";
+
+ private static final TypeReference PRINT_FUNCTION_TYPE_REFERENCE = TypeReference.findOrCreate(PythonTypes.pythonLoader,
+ "Lwala/builtin/" + PRINT_FUNCTION_VARIABLE_NAME);
+
+ public PythonModVisitorWithBuiltinFunctions(CGNode n, Collection result, ExtendedHeapModel h, PointerAnalysis pa,
+ boolean ignoreAllocHeapDefs) {
+ super(n, result, h, pa, ignoreAllocHeapDefs);
+ }
+
+ @Override
+ public void visitPythonInvoke(PythonInvokeInstruction inst) {
+ int use = inst.getUse(0); // a reference to the invoked function.
+ SSAInstruction def = this.n.getDU().getDef(use); // the definition of the invoked function.
+
+ if (def instanceof AstLexicalRead) {
+ AstLexicalRead read = (AstLexicalRead) def;
+ Access[] accesses = read.getAccesses();
+
+ if (accesses.length > 0 && accesses[0].variableName.equals(PRINT_FUNCTION_VARIABLE_NAME)) {
+ PointerKey pk = this.h.getPointerKeyForLocal(this.n, use);
+ OrdinalSet pointsToSet = this.pa.getPointsToSet(pk);
+
+ pointsToSet.forEach(ik -> {
+ TypeReference typeReference = getTypeReference(ik);
+
+ if (typeReference.equals(PRINT_FUNCTION_TYPE_REFERENCE)) {
+ // found a call to the built-in print function, which has side effects.
+ // add a pointer to a fake global variable representing a modification to the output stream.
+ this.result.add(GLOBAL_OUTPUT_STREAM_POINTER_KEY);
+ }
+ });
+ }
+ } else if (def instanceof PythonPropertyRead) {
+ PythonPropertyRead read = (PythonPropertyRead) def;
+ int memberRef = read.getMemberRef();
+ PointerKey memberRefPK = this.h.getPointerKeyForLocal(this.n, memberRef);
+ OrdinalSet memberRefPointsToSet = this.pa.getPointsToSet(memberRefPK);
+
+ memberRefPointsToSet.forEach(memberRefIK -> {
+ TypeReference typeReference = getTypeReference(memberRefIK);
+
+ if (typeReference.equals(string) && memberRefIK instanceof ConstantKey) {
+ ConstantKey> ck = (ConstantKey>) memberRefIK;
+ Object value = ck.getValue();
+
+ if (value.equals("append")) {
+ // check that the receiver is of type list.
+ int objectRef = read.getObjectRef();
+ PointerKey objectRefPK = this.h.getPointerKeyForLocal(this.n, objectRef);
+ OrdinalSet objectRefPointsToSet = this.pa.getPointsToSet(objectRefPK);
+
+ objectRefPointsToSet.forEach(objectRefIK -> {
+ if (objectRefIK.getConcreteType().getReference().equals(list))
+ // it's a list. Add the pointer to the results.
+ this.result.add(objectRefPK);
+ });
+ }
+ }
+ });
+ }
+
+ super.visitPythonInvoke(inst);
+ }
+
+ private TypeReference getTypeReference(T ik) {
+ return ik.getConcreteType().getReference();
+ }
+ }
+
+ @Override
+ protected ModVisitor makeModVisitor(CGNode n, Collection result,
+ PointerAnalysis pa, ExtendedHeapModel h, boolean ignoreAllocHeapDefs) {
+ return new PythonModVisitorWithBuiltinFunctions<>(n, result, h, pa, ignoreAllocHeapDefs);
+ }
+}
diff --git a/edu.cuny.hunter.hybridize.eval/META-INF/MANIFEST.MF b/edu.cuny.hunter.hybridize.eval/META-INF/MANIFEST.MF
index d65007340..fdd920402 100644
--- a/edu.cuny.hunter.hybridize.eval/META-INF/MANIFEST.MF
+++ b/edu.cuny.hunter.hybridize.eval/META-INF/MANIFEST.MF
@@ -7,6 +7,7 @@ Bundle-Version: 1.0.0.qualifier
Export-Package: edu.cuny.hunter.hybridize.eval.handlers,
edu.cuny.hunter.hybridize.eval.ui.plugins
Import-Package: com.google.common.collect,
+ com.ibm.wala.types,
org.python.pydev.parser.jython.ast,
org.python.pydev.plugin.nature
Require-Bundle: org.eclipse.ui,
diff --git a/edu.cuny.hunter.hybridize.eval/src/edu/cuny/hunter/hybridize/eval/handlers/EvaluateHybridizeFunctionRefactoringHandler.java b/edu.cuny.hunter.hybridize.eval/src/edu/cuny/hunter/hybridize/eval/handlers/EvaluateHybridizeFunctionRefactoringHandler.java
index 25d9fe2a9..c02e08016 100644
--- a/edu.cuny.hunter.hybridize.eval/src/edu/cuny/hunter/hybridize/eval/handlers/EvaluateHybridizeFunctionRefactoringHandler.java
+++ b/edu.cuny.hunter.hybridize.eval/src/edu/cuny/hunter/hybridize/eval/handlers/EvaluateHybridizeFunctionRefactoringHandler.java
@@ -13,6 +13,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.commons.csv.CSVPrinter;
@@ -35,7 +36,6 @@
import org.eclipse.ltk.core.refactoring.participants.ProcessorBasedRefactoring;
import org.eclipse.ui.ISources;
import org.eclipse.ui.handlers.HandlerUtil;
-import org.osgi.framework.FrameworkUtil;
import org.python.pydev.navigator.elements.PythonSourceFolder;
import com.google.common.collect.Sets;
@@ -45,7 +45,6 @@
import edu.cuny.citytech.refactoring.common.eval.handlers.EvaluateRefactoringHandler;
import edu.cuny.hunter.hybridize.core.analysis.Function;
import edu.cuny.hunter.hybridize.core.analysis.Function.HybridizationParameters;
-import edu.cuny.hunter.hybridize.core.analysis.PreconditionFailure;
import edu.cuny.hunter.hybridize.core.analysis.PreconditionSuccess;
import edu.cuny.hunter.hybridize.core.analysis.Refactoring;
import edu.cuny.hunter.hybridize.core.analysis.Transformation;
@@ -69,8 +68,12 @@ public class EvaluateHybridizeFunctionRefactoringHandler extends EvaluateRefacto
private static final String FAILED_PRECONDITIONS_CSV_FILENAME = "failed_preconditions.csv";
+ private static final String STATUS_CSV_FILENAME = "statuses.csv";
+
private static final String PERFORM_CHANGE_PROPERTY_KEY = "edu.cuny.hunter.hybridize.eval.performChange";
+ private static final String ALWAYS_CHECK_PYTHON_SIDE_EFFECTS_PROPERTY_KEY = "edu.cuny.hunter.hybridize.eval.alwaysCheckPythonSideEffects";
+
private static String[] buildAttributeColumnNames(String... additionalColumnNames) {
String[] primaryColumns = new String[] { "subject", "function", "module", "relative path" };
List ret = new ArrayList<>(Arrays.asList(primaryColumns));
@@ -81,13 +84,15 @@ private static String[] buildAttributeColumnNames(String... additionalColumnName
private static Object[] buildAttributeColumnValues(Function function, Object... additionalColumnValues) {
IProject project = function.getProject();
Path relativePath = project.getLocation().toFile().toPath().relativize(function.getContainingFile().toPath());
- String[] primaryColumns = new String[] { project.getName(), function.getIdentifier(), function.getContainingModuleName(),
- relativePath.toString() };
+ Object[] primaryColumns = new Object[] { project.getName(), function.getIdentifier(), function.getContainingModuleName(),
+ relativePath };
List