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 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 ret = new ArrayList<>(Arrays.asList(primaryColumns)); ret.addAll(Arrays.asList(additionalColumnValues)); return ret.toArray(Object[]::new); } + private boolean alwaysCheckPythonSideEffects = Boolean.getBoolean(ALWAYS_CHECK_PYTHON_SIDE_EFFECTS_PROPERTY_KEY); + @Override public Object execute(ExecutionEvent event) throws ExecutionException { Job.create("Evaluating Hybridize Functions refactoring...", monitor -> { @@ -113,7 +118,9 @@ public Object execute(ExecutionEvent event) throws ExecutionException { CSVPrinter optimizableFunctionPrinter = createCSVPrinter(OPTMIZABLE_CSV_FILENAME, buildAttributeColumnNames()); CSVPrinter nonOptimizableFunctionPrinter = createCSVPrinter(NONOPTMIZABLE_CSV_FILENAME, buildAttributeColumnNames()); CSVPrinter errorPrinter = createCSVPrinter(FAILED_PRECONDITIONS_CSV_FILENAME, - buildAttributeColumnNames("code", "message"));) { + buildAttributeColumnNames("refactoring", "severity", "code", "message")); + CSVPrinter statusPrinter = createCSVPrinter(STATUS_CSV_FILENAME, + buildAttributeColumnNames("refactoring", "severity", "code", "message"));) { IProject[] pythonProjectsFromEvent = getSelectedPythonProjectsFromEvent(event); monitor.beginTask("Analyzing projects...", pythonProjectsFromEvent.length); @@ -126,7 +133,8 @@ public Object execute(ExecutionEvent event) throws ExecutionException { TimeCollector resultsTimeCollector = new TimeCollector(); resultsTimeCollector.start(); - HybridizeFunctionRefactoringProcessor processor = createHybridizeFunctionRefactoring(new IProject[] { project }); + HybridizeFunctionRefactoringProcessor processor = createHybridizeFunctionRefactoring(new IProject[] { project }, + this.getAlwaysCheckPythonSideEffects()); resultsTimeCollector.stop(); // run the precondition checking. @@ -147,8 +155,8 @@ public Object execute(ExecutionEvent event) throws ExecutionException { // optimization available functions. These are the "filtered" functions. We consider functions to be candidates iff they // have a tensor-like parameter or are currently hybrid. Set candidates = functions.stream().filter(Function::isHybridizationAvailable) - .filter(f -> f.getStatus().getEntryMatchingCode(FrameworkUtil.getBundle(Function.class).getSymbolicName(), - PreconditionFailure.OPTIMIZATION_NOT_AVAILABLE.getCode()) == null) + .filter(f -> f.isHybrid() != null && f.isHybrid() + || f.getLikelyHasTensorParameter() != null && f.getLikelyHasTensorParameter()) .collect(Collectors.toSet()); resultsPrinter.print(candidates.size()); // number. @@ -175,40 +183,30 @@ public Object execute(ExecutionEvent event) throws ExecutionException { nonOptimizableFunctionPrinter.printRecord(buildAttributeColumnValues(function)); // failed preconditions. - Collection errorEntries = failures.parallelStream().map(Function::getStatus) - .flatMap(s -> Arrays.stream(s.getEntries())).filter(RefactoringStatusEntry::isError) - .collect(Collectors.toSet()); + Collection errorEntries = getRefactoringStatusEntries(failures, + RefactoringStatusEntry::isError); resultsPrinter.print(errorEntries.size()); // number. - for (RefactoringStatusEntry entry : errorEntries) { - if (!entry.isFatalError()) { - Object correspondingElement = entry.getData(); - - if (!(correspondingElement instanceof Function)) - throw new IllegalStateException("The element: " + correspondingElement - + " corresponding to a failed precondition is not a Function. Instead, it is a: " - + correspondingElement.getClass()); + printStatuses(errorPrinter, errorEntries); - Function failedFunction = (Function) correspondingElement; - - errorPrinter.printRecord(buildAttributeColumnValues(failedFunction, entry.getCode(), entry.getMessage())); - } - } + // general refactoring statuses. + Set generalEntries = getRefactoringStatusEntries(functions, x -> true); + printStatuses(statusPrinter, generalEntries); // refactoring type counts. for (Refactoring refactoringKind : Refactoring.values()) - resultsPrinter.print(functions.parallelStream().map(Function::getRefactoring) + resultsPrinter.print(candidates.parallelStream().map(Function::getRefactoring) .filter(r -> Objects.equals(r, refactoringKind)).count()); // precondition success counts. for (PreconditionSuccess preconditionSuccess : PreconditionSuccess.values()) - resultsPrinter.print(functions.parallelStream().map(Function::getPassingPrecondition) + resultsPrinter.print(candidates.parallelStream().map(Function::getPassingPrecondition) .filter(pp -> Objects.equals(pp, preconditionSuccess)).count()); // transformation counts. for (Transformation transformation : Transformation.values()) - resultsPrinter.print(functions.parallelStream().map(Function::getTransformations).filter(Objects::nonNull) + resultsPrinter.print(candidates.parallelStream().map(Function::getTransformations).filter(Objects::nonNull) .flatMap(as -> as.parallelStream()).filter(a -> Objects.equals(a, transformation)).count()); // actually perform the refactoring if there are no fatal errors. @@ -238,15 +236,39 @@ public Object execute(ExecutionEvent event) throws ExecutionException { return null; } + private static Set getRefactoringStatusEntries(Set functionSet, + Predicate predicate) { + return functionSet.parallelStream().map(Function::getStatus).flatMap(s -> Arrays.stream(s.getEntries())).filter(predicate) + .collect(Collectors.toSet()); + } + + private static void printStatuses(CSVPrinter printer, Collection entries) throws IOException { + for (RefactoringStatusEntry entry : entries) { + if (!entry.isFatalError()) { + Object correspondingElement = entry.getData(); + + if (!(correspondingElement instanceof Function)) + throw new IllegalStateException("The element: " + correspondingElement + " is not a Function. Instead, it is a: " + + correspondingElement.getClass()); + + Function function = (Function) correspondingElement; + + printer.printRecord(buildAttributeColumnValues(function, function.getRefactoring(), entry.getSeverity(), entry.getCode(), + entry.getMessage())); + } + } + } + private static String[] buildFunctionAttributeColumnNames() { - return buildAttributeColumnNames("parameters", "tensor parameter", "hybrid", "autograph", "experimental_autograph_options", - "experimental_follow_type_hints", "experimental_implements", "func", "input_signature", "jit_compile", "reduce_retracing", - "refactoring", "passing precondition", "status"); + return buildAttributeColumnNames("method reference", "type reference", "parameters", "tensor parameter", "hybrid", "side-effects", + "autograph", "experimental_autograph_options", "experimental_follow_type_hints", "experimental_implements", "func", + "input_signature", "jit_compile", "reduce_retracing", "refactoring", "passing precondition", "status"); } private static void printFunction(CSVPrinter printer, Function function) throws IOException { - Object[] initialColumnValues = buildAttributeColumnValues(function, function.getNumberOfParameters(), - function.getLikelyHasTensorParameter(), function.isHybrid()); + Object[] initialColumnValues = buildAttributeColumnValues(function, function.getMethodReference(), function.getDeclaringClass(), + function.getNumberOfParameters(), function.getLikelyHasTensorParameter(), function.isHybrid(), + function.getHasPythonSideEffects()); for (Object columnValue : initialColumnValues) printer.print(columnValue); @@ -322,4 +344,8 @@ private static IProject getProject(Object obj) { return null; } + + public boolean getAlwaysCheckPythonSideEffects() { + return alwaysCheckPythonSideEffects; + } } diff --git a/edu.cuny.hunter.hybridize.tests/.gitignore b/edu.cuny.hunter.hybridize.tests/.gitignore index b83d22266..9132ec700 100644 --- a/edu.cuny.hunter.hybridize.tests/.gitignore +++ b/edu.cuny.hunter.hybridize.tests/.gitignore @@ -1 +1,3 @@ /target/ +/file.txt +/spam.txt diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testModel7/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testModel7/in/A.py new file mode 100644 index 000000000..0146f58c7 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testModel7/in/A.py @@ -0,0 +1,53 @@ +import tensorflow as tf + +# Create an override model to classify pictures + + +class SequentialModel(tf.keras.Model): + + def __init__(self, **kwargs): + super(SequentialModel, self).__init__(**kwargs) + + self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28)) + + # Add a lot of small layers + num_layers = 100 + self.my_layers = [tf.keras.layers.Dense(64, activation="relu") + for n in range(num_layers)] + + self.dropout = tf.keras.layers.Dropout(0.2) + self.dense_2 = tf.keras.layers.Dense(10) + + self._stuff = 5 + + def call(self, x): + x = self.flatten(x) + + for layer in self.my_layers: + x = layer(x) + + x = self.dropout(x) + x = self.dense_2(x) + + self._stuff = 6 + + return x + + def get_stuff(self): + return self._stuff + + +input_data = tf.random.uniform([20, 28, 28]) +print("Input:") +print(type(input_data)) +print(input_data) + +model = SequentialModel() +print(model.get_stuff()) + +result = model.call(input_data) +print(model.get_stuff()) + +print("Output:") +print(type(input_data)) +print(result) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testModel7/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testModel7/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testModel7/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects/in/A.py new file mode 100644 index 000000000..d8a954964 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects/in/A.py @@ -0,0 +1,13 @@ +# From https://www.tensorflow.org/guide/function#executing_python_side_effects. + +import tensorflow as tf + + +def f(x): + print("Traced with", x) + tf.print("Executed with", x) + + +f(1) +f(1) +f(2) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects10/in/.gitignore b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects10/in/.gitignore new file mode 100644 index 000000000..37d479370 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects10/in/.gitignore @@ -0,0 +1 @@ +/spam.txt diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects10/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects10/in/A.py new file mode 100644 index 000000000..6f1e7481c --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects10/in/A.py @@ -0,0 +1,9 @@ +# From https://docs.python.org/3/library/io.html#io.IOBase. + + +def f(): + with open('spam.txt', 'w') as file: + file.write('Spam and eggs!') + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects10/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects10/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects11/in/.gitignore b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects11/in/.gitignore new file mode 100644 index 000000000..37d479370 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects11/in/.gitignore @@ -0,0 +1 @@ +/spam.txt diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects11/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects11/in/A.py new file mode 100644 index 000000000..8641cdb79 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects11/in/A.py @@ -0,0 +1,6 @@ +def f(): + with open('file.txt', 'w') as f: + f.writelines(['line1\n', 'line2\n']) + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects11/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects11/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects12/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects12/in/A.py new file mode 100644 index 000000000..279cd9e79 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects12/in/A.py @@ -0,0 +1,6 @@ +def f(): + my_list = [10] + my_list[0] = 1 + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects12/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects12/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects13/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects13/in/A.py new file mode 100644 index 000000000..3dfb5fb41 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects13/in/A.py @@ -0,0 +1,8 @@ +# From https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions + + +def f(): + squares = [x ** 2 for x in range(10)] + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects13/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects13/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects14/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects14/in/A.py new file mode 100644 index 000000000..1e3cae621 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects14/in/A.py @@ -0,0 +1,8 @@ +# From https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions + + +def f(): + squares = list(map(lambda x: x**2, range(10))) + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects14/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects14/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects15/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects15/in/A.py new file mode 100644 index 000000000..04b407573 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects15/in/A.py @@ -0,0 +1,10 @@ +# From https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions + + +def f(): + squares = [] + for x in range(10): + squares.append(x ** 2) + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects15/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects15/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects16/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects16/in/A.py new file mode 100644 index 000000000..2bfcb6846 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects16/in/A.py @@ -0,0 +1,15 @@ +# From https://stackoverflow.com/questions/5753597/is-it-pythonic-to-use-list-comprehensions-for-just-side-effects + +my_list = [10] + + +def fun_with_side_effects(y): + my_list[0] = 1 + return y ** 2 + + +def f(): + squares = [fun_with_side_effects(x) for x in range(10)] + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects16/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects16/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects17/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects17/in/A.py new file mode 100644 index 000000000..51c6a080c --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects17/in/A.py @@ -0,0 +1,15 @@ +# From https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions + +my_list = [10] + + +def fun_with_side_effects(y): + my_list[0] = 1 + return y ** 2 + + +def f(): + squares = list(map(lambda x: fun_with_side_effects(x), range(10))) + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects17/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects17/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects18/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects18/in/A.py new file mode 100644 index 000000000..b8c40d625 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects18/in/A.py @@ -0,0 +1,12 @@ +my_list = [10] + + +def g(): + my_list[0] = 1 + + +def f(): + g() + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects18/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects18/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects19/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects19/in/A.py new file mode 100644 index 000000000..00b2eb48a --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects19/in/A.py @@ -0,0 +1,10 @@ +def g(): + my_list = [10] + my_list[0] = 1 + + +def f(): + g() + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects19/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects19/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects2/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects2/in/A.py new file mode 100644 index 000000000..5a6b05645 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects2/in/A.py @@ -0,0 +1,13 @@ +# From https://www.tensorflow.org/guide/function#executing_python_side_effects. + +import tensorflow as tf + + +def f(x): + # print("Traced with", x) # This is a Python side-effect. + tf.print("Executed with", x) # THis isn't. + + +f(1) +f(1) +f(2) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects2/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects2/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects2/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects20/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects20/in/A.py new file mode 100644 index 000000000..4104acfa9 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects20/in/A.py @@ -0,0 +1,13 @@ +a = 10 + + +def g(): + global a + a = a + 1 + + +def f(): + g() + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects20/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects20/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects21/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects21/in/A.py new file mode 100644 index 000000000..b0fa77da3 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects21/in/A.py @@ -0,0 +1,10 @@ +def g(): + a = 10 + a = a + 1 + + +def f(): + g() + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects21/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects21/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects22/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects22/in/A.py new file mode 100644 index 000000000..35f60132b --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects22/in/A.py @@ -0,0 +1,12 @@ +# From https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions + + +def g(): + squares = [x ** 2 for x in range(10)] + + +def f(): + g() + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects22/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects22/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects23/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects23/in/A.py new file mode 100644 index 000000000..daaf7d09e --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects23/in/A.py @@ -0,0 +1,24 @@ +# From https://stackoverflow.com/questions/5753597/is-it-pythonic-to-use-list-comprehensions-for-just-side-effects + +my_list = [10] + + +def h(): + pass + + +def fun_with_side_effects(y): + my_list[0] = 1 + return y ** 2 + + +def g(): + squares = [fun_with_side_effects(x) for x in range(10)] + + +def f(): + g() + + +f() +h() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects23/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects23/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects24/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects24/in/A.py new file mode 100644 index 000000000..636ede0ed --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects24/in/A.py @@ -0,0 +1,12 @@ +# From https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions + + +def g(): + squares = list(map(lambda x: x ** 2, range(10))) + + +def f(): + g() + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects24/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects24/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects25/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects25/in/A.py new file mode 100644 index 000000000..ae6140329 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects25/in/A.py @@ -0,0 +1,14 @@ +# From https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions + + +def g(): + squares = [] + for x in range(10): + squares.append(x ** 2) + + +def f(): + g() + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects25/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects25/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects26/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects26/in/A.py new file mode 100644 index 000000000..9f8daec6c --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects26/in/A.py @@ -0,0 +1,11 @@ +# From https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions + +squares = [] + + +def f(): + for x in range(10): + squares.append(x ** 2) + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects26/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects26/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects27/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects27/in/A.py new file mode 100644 index 000000000..4064bc426 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects27/in/A.py @@ -0,0 +1,15 @@ +# From https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions + +squares = [] + + +def g(): + for x in range(10): + squares.append(x ** 2) + + +def f(): + g() + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects27/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects27/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects28/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects28/in/A.py new file mode 100644 index 000000000..7454e88ac --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects28/in/A.py @@ -0,0 +1,10 @@ +def g(a_list): + a_list[0] = 1 + + +def f(): + my_list = [10] + g(my_list) + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects28/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects28/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects29/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects29/in/A.py new file mode 100644 index 000000000..a94124fdc --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects29/in/A.py @@ -0,0 +1,15 @@ +a = 10 + + +def g(): + global a + a = 1 + + +def f(): + g() + + +assert a == 10 +f() +assert a == 1, "Function f() calls g(), function g() modifies a global variable." diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects29/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects29/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects3/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects3/in/A.py new file mode 100644 index 000000000..e743bea73 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects3/in/A.py @@ -0,0 +1,15 @@ +# From https://www.tensorflow.org/guide/function#executing_python_side_effects. + +import tensorflow as tf + + +def f(x): + # Assigning x to a." + a = x + print("Traced with", a) # This is a transitive Python side-effect. + tf.print("Executed with", x) # This isn't. + + +f(1) +f(1) +f(2) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects3/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects3/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects3/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects30/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects30/in/A.py new file mode 100644 index 000000000..4be27c3ec --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects30/in/A.py @@ -0,0 +1,15 @@ +a = 10 + + +def g(): + a = 1 + + +def f(): + g() + + +assert a == 10 +f() +assert a == 10 + diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects30/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects30/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects31/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects31/in/A.py new file mode 100644 index 000000000..4597b490c --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects31/in/A.py @@ -0,0 +1,14 @@ +a = [10] + + +def g(): + a[0] = 1 + + +def f(): + g() + + +assert a == [10] +f() +assert a == [1] diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects31/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects31/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects32/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects32/in/A.py new file mode 100644 index 000000000..258af26a4 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects32/in/A.py @@ -0,0 +1,21 @@ +a = 10 + + +def h(): + global a + a = 1 + + +def g(): + pass + + +def f(): + g() + + +assert a == 10 +f() +assert a == 10 +h() +assert a == 1 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects32/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects32/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects33/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects33/in/A.py new file mode 100644 index 000000000..80f93d473 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects33/in/A.py @@ -0,0 +1,12 @@ +def g(a_list): + return a_list[0] + + +def f(): + my_list = [10] + assert my_list[0] == 10 + g(my_list) + assert my_list[0] == 10 + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects33/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects33/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects34/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects34/in/A.py new file mode 100644 index 000000000..1f8fee694 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects34/in/A.py @@ -0,0 +1,12 @@ +def g(a): + a = 1 + + +def f(): + x = 10 + assert x == 10 + g(x) + assert x == 10 + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects34/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects34/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects35/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects35/in/A.py new file mode 100644 index 000000000..1c25902ec --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects35/in/A.py @@ -0,0 +1,17 @@ +# From https://www.tensorflow.org/guide/function#changing_python_global_and_free_variables. +import tensorflow as tf + +external_list = [] + + +@tf.function +def side_effect(x): + tf.print('Python side effect') + external_list.append(x) + + +side_effect(1) +side_effect(1) +side_effect(1) +# The list append only happened once! +assert len(external_list) == 1 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects35/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects35/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects35/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects36/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects36/in/A.py new file mode 100644 index 000000000..42d7e1929 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects36/in/A.py @@ -0,0 +1,17 @@ +# From https://www.tensorflow.org/guide/function#changing_python_global_and_free_variables. +# No printing in this one. +import tensorflow as tf + +external_list = [] + + +@tf.function +def side_effect(x): + external_list.append(x) + + +side_effect(1) +side_effect(1) +side_effect(1) +# The list append only happened once! +assert len(external_list) == 1 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects36/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects36/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects36/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects37/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects37/in/A.py new file mode 100644 index 000000000..ac0084f1d --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects37/in/A.py @@ -0,0 +1,17 @@ +# From https://www.tensorflow.org/guide/function#changing_python_global_and_free_variables. +import tensorflow as tf + +external_list = [1, 3, 5] + + +@tf.function +def no_side_effect(x): + tf.print('No python side effect') + return external_list[x] + +assert len(external_list) == 3 +no_side_effect(1) +no_side_effect(1) +no_side_effect(1) +# The list append only happened once! +assert len(external_list) == 3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects37/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects37/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects37/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects38/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects38/in/A.py new file mode 100644 index 000000000..9b8666b64 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects38/in/A.py @@ -0,0 +1,23 @@ +# From https://www.tensorflow.org/guide/function#changing_python_global_and_free_variables. +import tensorflow as tf + + +class Model(tf.Module): + + def __init__(self): + self.v = tf.Variable(0) + self.counter = 0 + + @tf.function + def __call__(self): + if self.counter == 0: + # A python side-effect + self.counter += 1 + self.v.assign_add(1) + + return self.v + + +m = Model() +for n in range(3): + print(m().numpy()) # prints 1, 2, 3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects38/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects38/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects38/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects39/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects39/in/A.py new file mode 100644 index 000000000..a81c328c1 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects39/in/A.py @@ -0,0 +1,23 @@ +# From https://www.tensorflow.org/guide/function#changing_python_global_and_free_variables. +import tensorflow as tf + + +class Model(tf.Module): + + def __init__(self): + self.v = tf.Variable(0) + self.counter = 0 + + @tf.function + def __call__(self): + if self.counter == 0: + # A python side-effect + self.counter += 1 + self.v.assign_add(1) + + return self.v + + +m = Model() +for n in range(3): + print(m.__call__().numpy()) # prints 1, 2, 3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects39/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects39/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects39/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects4/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects4/in/A.py new file mode 100644 index 000000000..2bb9174ee --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects4/in/A.py @@ -0,0 +1,10 @@ +# From https://www.tensorflow.org/guide/function#executing_python_side_effects. + + +def f(x): + x = 5 # no side-effect as `x` is a local variable.. + + +f(1) +f(1) +f(2) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects4/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects4/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects40/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects40/in/A.py new file mode 100644 index 000000000..4aee72880 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects40/in/A.py @@ -0,0 +1,16 @@ +# From https://www.tensorflow.org/guide/function#using_python_iterators_and_generators + +import tensorflow as tf + + +@tf.function +def buggy_consume_next(iterator): + tf.print("Value:", next(iterator)) + + +iterator = iter([1, 2, 3]) + +buggy_consume_next(iterator) +# This reuses the first value from the iterator, rather than consuming the next value. +buggy_consume_next(iterator) +buggy_consume_next(iterator) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects40/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects40/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects40/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects41/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects41/in/A.py new file mode 100644 index 000000000..d3f32ad38 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects41/in/A.py @@ -0,0 +1,16 @@ +# From https://www.tensorflow.org/guide/function#using_python_iterators_and_generators + +import tensorflow as tf + + +@tf.function +def good_consume_next(iterator): + # This is ok, iterator is a tf.data.Iterator + tf.print("Value:", next(iterator)) + + +ds = tf.data.Dataset.from_tensor_slices([1, 2, 3]) +iterator = iter(ds) +good_consume_next(iterator) +good_consume_next(iterator) +good_consume_next(iterator) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects41/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects41/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects41/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects42/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects42/in/A.py new file mode 100644 index 000000000..61d032f7d --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects42/in/A.py @@ -0,0 +1,21 @@ +# From https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values + +import tensorflow as tf + +x = None + + +@tf.function +def leaky_function(a): + global x + x = a + 1 # Bad - leaks local tensor + return a + 2 + + +correct_a = leaky_function(tf.constant(1)) + +print(correct_a.numpy()) # Good - value obtained from function's returns +try: + x.numpy() # Bad - tensor leaked from inside the function, cannot be used here +except AttributeError as expected: + print(expected) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects42/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects42/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects42/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects43/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects43/in/A.py new file mode 100644 index 000000000..97f300cfa --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects43/in/A.py @@ -0,0 +1,21 @@ +# From https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values + +import tensorflow as tf + +x = None + + +@tf.function +def leaky_function(a): + global x + # x = a + 1 # Bad - leaks local tensor + return a + 2 + + +correct_a = leaky_function(tf.constant(1)) + +print(correct_a.numpy()) # Good - value obtained from function's returns +# try: +# x.numpy() # Bad - tensor leaked from inside the function, cannot be used here +# except AttributeError as expected: +# print(expected) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects43/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects43/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects43/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects44/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects44/in/A.py new file mode 100644 index 000000000..5603ac314 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects44/in/A.py @@ -0,0 +1,21 @@ +# From https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values + +import tensorflow as tf + +x = None + + +# @tf.function +def leaky_function(a): + global x + # x = a + 1 # Bad - leaks local tensor + return a + 2 + + +correct_a = leaky_function(tf.constant(1)) + +print(correct_a.numpy()) # Good - value obtained from function's returns +# try: +# x.numpy() # Bad - tensor leaked from inside the function, cannot be used here +# except AttributeError as expected: +# print(expected) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects44/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects44/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects44/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects45/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects45/in/A.py new file mode 100644 index 000000000..177f430f7 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects45/in/A.py @@ -0,0 +1,21 @@ +# From https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values + +import tensorflow as tf + +x = None + + +@tf.function +def leaky_function(a): + global x + x = a + 1 # Bad - leaks local tensor + # return a + 2 + + +correct_a = leaky_function(tf.constant(1)) + +# print(correct_a.numpy()) # Good - value obtained from function's returns +try: + x.numpy() # Bad - tensor leaked from inside the function, cannot be used here +except AttributeError as expected: + print(expected) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects45/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects45/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects45/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects46/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects46/in/A.py new file mode 100644 index 000000000..e94607a9e --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects46/in/A.py @@ -0,0 +1,21 @@ +# From https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values + +import tensorflow as tf + +x = None + + +# @tf.function +def leaky_function(a): + global x + x = a + 1 # Bad - leaks local tensor + # return a + 2 + + +correct_a = leaky_function(tf.constant(1)) + +# print(correct_a.numpy()) # Good - value obtained from function's returns +try: + x.numpy() # Bad - tensor leaked from inside the function, cannot be used here +except AttributeError as expected: + print(expected) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects46/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects46/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects46/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects47/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects47/in/A.py new file mode 100644 index 000000000..75032e3d9 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects47/in/A.py @@ -0,0 +1,30 @@ +# From https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values + +import tensorflow as tf +from nose.tools import assert_raises + + +@tf.function +def leaky_function(a): + global x + x = a + 1 # Bad - leaks local tensor + return x # Good - uses local tensor + + +correct_a = leaky_function(tf.constant(1)) + +print(correct_a.numpy()) # Good - value obtained from function's returns +try: + x.numpy() # Bad - tensor leaked from inside the function, cannot be used here +except AttributeError as expected: + print(expected) + + +@tf.function +def captures_leaked_tensor(b): + b += x # Bad - `x` is leaked from `leaky_function` + return b + + +with assert_raises(TypeError): + captures_leaked_tensor(tf.constant(2)) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects47/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects47/in/requirements.txt new file mode 100644 index 000000000..56020dd04 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects47/in/requirements.txt @@ -0,0 +1,2 @@ +tensorflow==2.9.3 +nose==1.3.7 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects48/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects48/in/A.py new file mode 100644 index 000000000..e58c67b14 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects48/in/A.py @@ -0,0 +1,30 @@ +# From https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values + +import tensorflow as tf +from nose.tools import assert_raises + + +@tf.function +def leaky_function(a): + global x + x = a + 1 # Bad - leaks local tensor + return x # Good - uses local tensor + + +correct_a = leaky_function(tf.constant(1)) + +print(correct_a.numpy()) # Good - value obtained from function's returns +try: + x.numpy() # Bad - tensor leaked from inside the function, cannot be used here +except AttributeError as expected: + print(expected) + + +# @tf.function +def captures_leaked_tensor(b): + b += x # Bad - `x` is leaked from `leaky_function` + return b + + +with assert_raises(TypeError): + captures_leaked_tensor(tf.constant(2)) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects48/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects48/in/requirements.txt new file mode 100644 index 000000000..56020dd04 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects48/in/requirements.txt @@ -0,0 +1,2 @@ +tensorflow==2.9.3 +nose==1.3.7 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects49/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects49/in/A.py new file mode 100644 index 000000000..188ac7a96 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects49/in/A.py @@ -0,0 +1,21 @@ +# From https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values + +import tensorflow as tf + +x = None + + +# @tf.function +def leaky_function(a): + global x + x = a + 1 # Bad - leaks local tensor + return a + 2 + + +correct_a = leaky_function(tf.constant(1)) + +print(correct_a.numpy()) # Good - value obtained from function's returns +try: + x.numpy() # Bad - tensor leaked from inside the function, cannot be used here +except AttributeError as expected: + print(expected) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects49/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects49/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects49/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects5/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects5/in/A.py new file mode 100644 index 000000000..afe6aa25c --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects5/in/A.py @@ -0,0 +1,8 @@ +my_list = [10] + + +def f(): + my_list[0] = 1 + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects5/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects5/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects50/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects50/in/A.py new file mode 100644 index 000000000..dcb2d4daa --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects50/in/A.py @@ -0,0 +1,29 @@ +# From https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values + +import tensorflow as tf + + +class MyClass: + + def __init__(self): + self.field = None + + +external_list = [] +external_object = MyClass() + + +def leaky_function(): + a = tf.constant(1) + external_list.append(a) # Bad - leaks tensor + external_object.field = a # Bad - leaks tensor + + +assert len(external_list) == 0 +assert external_object is not None +assert external_object.field is None +leaky_function() +assert len(external_list) == 1 +assert external_object is not None +assert external_object.field is not None +assert external_object.field == tf.constant(1) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects50/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects50/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects50/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects51/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects51/in/A.py new file mode 100644 index 000000000..8b17b3cb6 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects51/in/A.py @@ -0,0 +1,28 @@ +# From https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values + +import tensorflow as tf + + +class MyClass: + + def __init__(self): + self.field = None + + +external_list = [] +# external_object = MyClass() + + +def leaky_function(): + a = tf.constant(1) + external_list.append(a) # Bad - leaks tensor + # external_object.field = a # Bad - leaks tensor + +assert len(external_list) == 0 +# assert external_object is not None +# assert external_object.field is None +leaky_function() +assert len(external_list) == 1 +# assert external_object is not None +# assert external_object.field is not None +# assert external_object.field == tf.constant(1) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects51/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects51/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects51/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects52/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects52/in/A.py new file mode 100644 index 000000000..2049545f4 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects52/in/A.py @@ -0,0 +1,28 @@ +# From https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values + +import tensorflow as tf + + +class MyClass: + + def __init__(self): + self.field = None + + +# external_list = [] +external_object = MyClass() + + +def leaky_function(): + a = tf.constant(1) + # external_list.append(a) # Bad - leaks tensor + external_object.field = a # Bad - leaks tensor + +# assert len(external_list) == 0 +assert external_object is not None +assert external_object.field is None +leaky_function() +# assert len(external_list) == 1 +assert external_object is not None +assert external_object.field is not None +assert external_object.field == tf.constant(1) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects52/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects52/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects52/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects53/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects53/in/A.py new file mode 100644 index 000000000..34173b8e8 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects53/in/A.py @@ -0,0 +1,28 @@ +# From https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values + +import tensorflow as tf + + +class MyClass: + + def __init__(self): + self.field = None + + +# external_list = [] +external_object = MyClass() + + +def not_leaky_function(): + a = tf.constant(1) + # external_list.append(a) # Bad - leaks tensor + return external_object.field + + +# assert len(external_list) == 0 +assert external_object is not None +assert external_object.field is None +not_leaky_function() +# assert len(external_list) == 1 +assert external_object is not None +assert external_object.field is None diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects53/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects53/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects53/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects54/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects54/in/A.py new file mode 100644 index 000000000..483cea0bc --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects54/in/A.py @@ -0,0 +1,33 @@ +# From https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values + +import tensorflow as tf +from nose.tools import assert_raises + + +class MyClass: + + def __init__(self): + self.field = None + + +external_list = [] +external_object = MyClass() + + +@tf.function +def leaky_function(): + a = tf.constant(1) + external_list.append(a) # Bad - leaks tensor + external_object.field = a # Bad - leaks tensor + + +assert len(external_list) == 0 +assert external_object is not None +assert external_object.field is None +leaky_function() +assert len(external_list) == 1 +assert external_object is not None +assert external_object.field is not None + +with assert_raises(TypeError): + assert external_object.field == tf.constant(1) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects54/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects54/in/requirements.txt new file mode 100644 index 000000000..56020dd04 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects54/in/requirements.txt @@ -0,0 +1,2 @@ +tensorflow==2.9.3 +nose==1.3.7 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects55/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects55/in/A.py new file mode 100644 index 000000000..c97ab24ed --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects55/in/A.py @@ -0,0 +1,30 @@ +# From https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values + +import tensorflow as tf + + +class MyClass: + + def __init__(self): + self.field = None + + +external_list = [] +external_object = MyClass() + + +@tf.function +def leaky_function(): + a = tf.constant(1) + # external_list.append(a) # Bad - leaks tensor + # external_object.field = a # Bad - leaks tensor + return a + + +assert len(external_list) == 0 +assert external_object is not None +assert external_object.field is None +leaky_function() +assert len(external_list) == 0 +assert external_object is not None +assert external_object.field is None diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects55/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects55/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects55/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects56/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects56/in/A.py new file mode 100644 index 000000000..9da8e1ec1 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects56/in/A.py @@ -0,0 +1,9 @@ +def g(p1, p2): + assert p1 == 5 and p2 == 2 + + +def f(): + g(5, p2=2) + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects56/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects56/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects57/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects57/in/A.py new file mode 100644 index 000000000..9daede908 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects57/in/A.py @@ -0,0 +1,10 @@ +def f(): + + def g(): + return 5 + + a = g() + assert a == 5 + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects57/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects57/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects58/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects58/in/A.py new file mode 100644 index 000000000..a2e17dc04 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects58/in/A.py @@ -0,0 +1,14 @@ +import tensorflow as tf + + +def f(): + + @tf.function + def g(): + return 5 + + a = g() + assert a == 5 + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects58/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects58/in/requirements.txt new file mode 100644 index 000000000..b154f958f --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects58/in/requirements.txt @@ -0,0 +1 @@ +tensorflow==2.9.3 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects6/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects6/in/A.py new file mode 100644 index 000000000..61ad64c04 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects6/in/A.py @@ -0,0 +1,9 @@ +my_list = [10] + + +def f(): + my_list[0] = 1 + + +f() +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects6/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects6/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects7/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects7/in/A.py new file mode 100644 index 000000000..b8c40d625 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects7/in/A.py @@ -0,0 +1,12 @@ +my_list = [10] + + +def g(): + my_list[0] = 1 + + +def f(): + g() + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects7/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects7/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects8/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects8/in/A.py new file mode 100644 index 000000000..b370344f0 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects8/in/A.py @@ -0,0 +1,7 @@ +from B import g + +def f(): + g() + + +f() diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects8/in/B.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects8/in/B.py new file mode 100644 index 000000000..e83bf9547 --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects8/in/B.py @@ -0,0 +1,5 @@ +my_list = [10] + + +def g(): + my_list[0] = 1 diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects8/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects8/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects9/in/A.py b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects9/in/A.py new file mode 100644 index 000000000..a2200f8ed --- /dev/null +++ b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects9/in/A.py @@ -0,0 +1,5 @@ +def f(x): + print("Traced with: " + str(x)) + + +f(1) diff --git a/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects9/in/requirements.txt b/edu.cuny.hunter.hybridize.tests/resources/HybridizeFunction/testPythonSideEffects9/in/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/edu.cuny.hunter.hybridize.tests/test cases/edu/cuny/hunter/hybridize/tests/HybridizeFunctionRefactoringTest.java b/edu.cuny.hunter.hybridize.tests/test cases/edu/cuny/hunter/hybridize/tests/HybridizeFunctionRefactoringTest.java index 8c5178e3f..523ba0308 100644 --- a/edu.cuny.hunter.hybridize.tests/test cases/edu/cuny/hunter/hybridize/tests/HybridizeFunctionRefactoringTest.java +++ b/edu.cuny.hunter.hybridize.tests/test cases/edu/cuny/hunter/hybridize/tests/HybridizeFunctionRefactoringTest.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -40,6 +41,7 @@ import org.eclipse.jface.text.IDocument; import org.eclipse.ltk.core.refactoring.RefactoringCore; import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry; import org.eclipse.ltk.core.refactoring.participants.ProcessorBasedRefactoring; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -96,6 +98,7 @@ import edu.cuny.hunter.hybridize.core.analysis.Function; import edu.cuny.hunter.hybridize.core.analysis.FunctionDefinition; import edu.cuny.hunter.hybridize.core.analysis.FunctionExtractor; +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; @@ -140,6 +143,17 @@ public int getInterpreterType() throws CoreException { private static final String TF_FUNCTION_FQN = "tensorflow.python.eager.def_function.function"; + /** + * Check Python side-effects regardless if it's a candidate. + */ + private static final boolean ALWAYS_CHECK_PYTHON_SIDE_EFFECTS = true; + + /** + * Whether we should run the function processing in parallel. Running in parallel makes the logs difficult to read and doesn't offer + * much in way of speedup since each test has only a few {@link Function}s. + */ + private static final boolean PROCESS_FUNCTIONS_IN_PARALLEL = false; + /** * Add a module to the given {@link IPythonNature}. * @@ -547,7 +561,8 @@ private Set getFunctions(String fileNameWithoutExtension) throws Excep Set inputFunctionDefinitions = availableFunctionDefs.stream() .map(f -> new FunctionDefinition(f, fileNameWithoutExtension, inputTestFile, document, nature)).collect(Collectors.toSet()); - HybridizeFunctionRefactoringProcessor processor = new HybridizeFunctionRefactoringProcessor(inputFunctionDefinitions); + HybridizeFunctionRefactoringProcessor processor = new HybridizeFunctionRefactoringProcessor(inputFunctionDefinitions, + ALWAYS_CHECK_PYTHON_SIDE_EFFECTS, PROCESS_FUNCTIONS_IN_PARALLEL); ProcessorBasedRefactoring refactoring = new ProcessorBasedRefactoring(processor); @@ -570,6 +585,17 @@ private Set getFunctions() throws Exception { return getFunctions("A"); } + /** + * Returns the first {@link Function} in the default test file with the given identifier. + * + * @param functionIndentifier The {@link Function} to return. + * @return The first {@link Function} in the default test file with the given identifier. + */ + private Function getFunction(String functionIndentifier) throws Exception { + Set functions = this.getFunctions(); + return functions.stream().filter(f -> f.getIdentifier().equals(functionIndentifier)).findFirst().orElseThrow(); + } + /** * Return the {@link File} representing X.py, where X is fileNameWithoutExtension. * @@ -607,6 +633,15 @@ public void testAmbiguousDefinition() throws Exception { assertNotNull(function); assertFalse(function.isHybrid()); assertFalse(function.getLikelyHasTensorParameter()); + + switch (function.getIdentifier()) { + case "Test.value": + case "Test.name": + checkSideEffectStatus(function); + break; + default: + break; + } } } @@ -819,6 +854,7 @@ public void testComputeParameters10() throws Exception { // to return False. assertNull(args); + checkSideEffectStatus(function); } /** @@ -843,6 +879,17 @@ public void testComputeParameters11() throws Exception { assertTrue(!args.getFuncParamExists() && !args.getInputSignatureParamExists() & args.getAutoGraphParamExists() && !args.getJitCompileParamExists() && !args.getReduceRetracingParamExists() && !args.getExperimentalImplementsParamExists() && !args.getExperimentalAutographOptParamExists() && !args.getExperimentalFollowTypeHintsParamExists()); + + checkSideEffectStatus(function); + } + + private static void checkSideEffectStatus(Function function) { + RefactoringStatus status = function.getStatus(); + assertTrue("Should fail due to a call graph issue, either a decorated function or missing function invocation.", status.hasError()); + assertNull(function.getHasPythonSideEffects()); + RefactoringStatusEntry entry = status.getEntryMatchingCode(Function.PLUGIN_ID, + PreconditionFailure.UNDETERMINABLE_SIDE_EFFECTS.getCode()); + assertNotNull(entry); } /** @@ -996,9 +1043,14 @@ public void testIsHybrid3() throws Exception { Set functions = this.getFunctions(); assertNotNull(functions); assertEquals(2, functions.size()); // one function is for the decorator. - Function function = functions.iterator().next(); - assertNotNull(function); - assertFalse(function.isHybrid()); + + functions.stream().forEach(f -> { + assertNotNull(f); + assertFalse(f.isHybrid()); + + if (f.getIdentifier().equals("func1")) + checkSideEffectStatus(f); + }); } /** @@ -1012,6 +1064,7 @@ public void testIsHybrid4() throws Exception { Function function = functions.iterator().next(); assertNotNull(function); assertFalse(function.isHybrid()); + checkSideEffectStatus(function); } /** @@ -1038,6 +1091,7 @@ public void testIsHybrid6() throws Exception { Function function = functions.iterator().next(); assertNotNull(function); assertFalse(function.isHybrid()); + checkSideEffectStatus(function); } /** @@ -1064,6 +1118,7 @@ public void testIsHybrid8() throws Exception { Function function = functions.iterator().next(); assertNotNull(function); assertTrue(function.isHybrid()); + checkSideEffectStatus(function); } /** @@ -1091,6 +1146,9 @@ public void testIsHybridFalse() throws Exception { for (Function func : functions) { assertNotNull(func); assertFalse(func.isHybrid()); + + if (func.getIdentifier().equals("dummy_func2")) + checkSideEffectStatus(func); } } @@ -1106,6 +1164,7 @@ public void testIsHybridMultipleAttributes() throws Exception { for (Function func : functions) { assertNotNull(func); assertFalse(func.isHybrid()); + checkSideEffectStatus(func); } } @@ -1121,6 +1180,7 @@ public void testIsHybridMultipleDecorators() throws Exception { for (Function func : functions) { assertNotNull(func); assertTrue(func.isHybrid()); + checkSideEffectStatus(func); } } @@ -1164,6 +1224,7 @@ public void testProcessDecorator() throws Exception { Function function = functions.iterator().next(); assertNotNull(function); assertTrue(function.isHybrid()); + checkSideEffectStatus(function); } /** @@ -2085,9 +2146,6 @@ public void testHasLikelyTensorParameter17() throws Exception { } } - // TODO: Left off at https://www.tensorflow.org/guide/function#changing_python_global_and_free_variables. The model is not going to work - // because call() is called implicitly. See https://github.com/wala/ML/issues/24. - /** * Test for #2. From https://www.tensorflow.org/versions/r2.9/api_docs/python/tf/function#features. Example with closures. */ @@ -4354,10 +4412,15 @@ public void testModel() throws Exception { switch (simpleName) { case "__init__": assertFalse("Expecting " + simpleName + " to not have a tensor param.", f.getLikelyHasTensorParameter()); + assertFalse(f.isHybrid()); + assertTrue(f.getHasPythonSideEffects()); + checkOptimizationNotAvailableStatus(f); break; case "__call__": - // TODO: Change to assertTrue when https://github.com/wala/ML/issues/24 is fixed. + // NOTE: Change to assertTrue when https://github.com/wala/ML/issues/24 is fixed. assertFalse("Expecting " + simpleName + " to not have a tensor param.", f.getLikelyHasTensorParameter()); + // NOTE: Should be error-free once https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/271 is fixed. + checkSideEffectStatus(f); break; default: throw new IllegalStateException("Not expecting function: " + simpleName + "."); @@ -4386,10 +4449,15 @@ public void testModel2() throws Exception { switch (simpleName) { case "__init__": assertFalse("Expecting " + simpleName + " to not have a tensor param.", f.getLikelyHasTensorParameter()); + assertFalse(f.isHybrid()); + assertTrue(f.getHasPythonSideEffects()); + checkOptimizationNotAvailableStatus(f); break; case "call": - // TODO: Change to assertTrue when https://github.com/wala/ML/issues/24 is fixed. + // NOTE: Change to assertTrue when https://github.com/wala/ML/issues/24 is fixed. assertFalse("Expecting " + simpleName + " to not have a tensor param.", f.getLikelyHasTensorParameter()); + // NOTE: Remove once https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/271 is fixed. + checkSideEffectStatus(f); break; default: throw new IllegalStateException("Not expecting function: " + simpleName + "."); @@ -4417,9 +4485,14 @@ public void testModel3() throws Exception { switch (simpleName) { case "__init__": assertFalse("Expecting " + simpleName + " to not have a tensor param.", f.getLikelyHasTensorParameter()); + assertFalse(f.isHybrid()); + assertTrue(f.getHasPythonSideEffects()); + checkOptimizationNotAvailableStatus(f); break; case "call": assertTrue("Expecting " + simpleName + " to have a tensor param.", f.getLikelyHasTensorParameter()); + assertTrue("Should pass preconditions.", f.getStatus().isOK()); + assertFalse("No Python side-effects.", f.getHasPythonSideEffects()); break; default: throw new IllegalStateException("Not expecting function: " + simpleName + "."); @@ -4427,6 +4500,13 @@ public void testModel3() throws Exception { }); } + private static void checkOptimizationNotAvailableStatus(Function f) { + RefactoringStatus status = f.getStatus(); + assertTrue("Should not be available for optimization.", status.hasError()); + RefactoringStatusEntry noTensorsFailure = f.getEntryMatchingFailure(PreconditionFailure.HAS_NO_TENSOR_PARAMETERS); + assertTrue(!f.isHybrid() || (noTensorsFailure != null && noTensorsFailure.isError())); + } + /** * Test a model. No tf.function in this one. Explicit call method. */ @@ -4447,9 +4527,14 @@ public void testModel4() throws Exception { switch (simpleName) { case "__init__": assertFalse("Expecting " + simpleName + " to not have a tensor param.", f.getLikelyHasTensorParameter()); + assertFalse(f.isHybrid()); + assertTrue(f.getHasPythonSideEffects()); + checkOptimizationNotAvailableStatus(f); break; case "__call__": assertTrue("Expecting " + simpleName + " to have a tensor param.", f.getLikelyHasTensorParameter()); + assertTrue("Should pass preconditions.", f.getStatus().isOK()); + assertFalse("No Python side-effects.", f.getHasPythonSideEffects()); break; default: throw new IllegalStateException("Not expecting function: " + simpleName + "."); @@ -4477,10 +4562,15 @@ public void testModel5() throws Exception { switch (simpleName) { case "__init__": assertFalse("Expecting " + simpleName + " to not have a tensor param.", f.getLikelyHasTensorParameter()); + assertFalse(f.isHybrid()); + assertTrue(f.getHasPythonSideEffects()); + checkOptimizationNotAvailableStatus(f); break; case "call": - // TODO: Change to assertTrue once https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/229 is fixed. + // NOTE: Change to assertTrue once https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/229 is fixed. assertFalse("Expecting " + simpleName + " not to have a tensor param.", f.getLikelyHasTensorParameter()); + // Can't infer side-effects here because there's no invocation of this method. + checkSideEffectStatus(f); break; default: throw new IllegalStateException("Not expecting function: " + simpleName + "."); @@ -4508,10 +4598,58 @@ public void testModel6() throws Exception { switch (simpleName) { case "__init__": assertFalse("Expecting " + simpleName + " to not have a tensor param.", f.getLikelyHasTensorParameter()); + assertFalse(f.isHybrid()); + assertTrue(f.getHasPythonSideEffects()); + checkOptimizationNotAvailableStatus(f); break; case "__call__": - // TODO: Change to assertTrue once https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/229 is fixed. + // NOTE: Change to assertTrue once https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/229 is fixed. assertFalse("Expecting " + simpleName + " not to have a tensor param.", f.getLikelyHasTensorParameter()); + // No invocation, so we won't be able to infer side-effects. + checkSideEffectStatus(f); + break; + default: + throw new IllegalStateException("Not expecting function: " + simpleName + "."); + } + }); + } + + /** + * Test a model. No tf.function in this one. Explicit call method. Unlike testModel3, there are Python side-effects in + * SequentialModel.__init__() and SequentialModel.call(). + * + * @see HybridizeFunctionRefactoringTest#testModel3 + */ + @Test + public void testModel7() throws Exception { + Set functions = this.getFunctions(); + assertNotNull(functions); + + LOG.info("Found functions: " + functions.size()); + assertEquals("Expecting two functions.", 3, functions.size()); + + // no hybrids. + assertTrue(functions.stream().map(Function::isHybrid).allMatch(b -> b == false)); + + // check function parameters. + functions.forEach(f -> { + String simpleName = f.getSimpleName(); + switch (simpleName) { + case "__init__": + assertFalse("Expecting " + simpleName + " to not have a tensor param.", f.getLikelyHasTensorParameter()); + assertFalse(f.isHybrid()); + assertTrue(f.getHasPythonSideEffects()); + checkOptimizationNotAvailableStatus(f); + break; + case "get_stuff": + assertFalse("Expecting " + simpleName + " to not have a tensor param.", f.getLikelyHasTensorParameter()); + assertFalse(f.isHybrid()); + assertFalse(f.getHasPythonSideEffects()); + checkOptimizationNotAvailableStatus(f); + break; + case "call": + assertTrue("Expecting " + simpleName + " to have a tensor param.", f.getLikelyHasTensorParameter()); + assertTrue("Should have python side-effects.", f.getHasPythonSideEffects()); break; default: throw new IllegalStateException("Not expecting function: " + simpleName + "."); @@ -4556,4 +4694,769 @@ public void testPreconditionChecking2() throws Exception { // it's not hybrid but it has a tensor parameter. Let's make it hybrid. testPreconditionCheckingHelper(false, true, Refactoring.CONVERT_EAGER_FUNCTION_TO_HYBRID, Transformation.CONVERT_TO_HYBRID, P1); } + + @Test + public void testPythonSideEffects() throws Exception { + Function function = getSingleFunction(); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); // the example uses a primitive type. + assertTrue("Expecting a Python side-effect.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects2() throws Exception { + Function function = getSingleFunction(); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); // the example uses a primitive type. + // there's a call to a TF operation. So, no "Python" side-effects. + assertFalse("TF operations shouldn't be considered Python side-effects.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects3() throws Exception { + Function function = getSingleFunction(); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); // the example uses a primitive type. + // there's a transitive Python side-effect. + assertTrue("Expecting a Python side-effect from a transitive local variable.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects4() throws Exception { + Function function = getSingleFunction(); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); // the example uses a primitive type. + // there's a Python statement but no side-effect. + assertFalse("This Python statement only modifies a local variable, so no side-effects.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects5() throws Exception { + Function function = getSingleFunction(); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // there's a Python statement with side-effects. + assertTrue("This Python statement modifies a global variable, so it has side-effects.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects6() throws Exception { + Function function = getSingleFunction(); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // there's a Python statement with side-effects. Multiple calls to the function. + assertTrue("This Python statement modifies a global variable, so it has side-effects.", function.getHasPythonSideEffects()); + } + + /** + * Test transitive side-effects in the same file. + */ + @Test + public void testPythonSideEffects7() throws Exception { + Set functionSet = getFunctions(); + assertEquals(2, functionSet.size()); + testTransitivePythonSideEffects(functionSet); + } + + private static void testTransitivePythonSideEffects(Set functionSet) { + functionSet.forEach(f -> { + assertFalse(f.isHybrid()); + assertFalse(f.getLikelyHasTensorParameter()); + + switch (f.getIdentifier()) { + case "f": + case "g": + // there's a Python statement with (transitive) side-effects. + assertTrue("This Python statement modifies a global variable, so it has side-effects.", f.getHasPythonSideEffects()); + break; + + default: + fail("Not expecting: " + f.getIdentifier() + "."); + break; + } + }); + } + + /** + * Returns the only function defined in the default test file. + * + * @return The only function defined in the default test file. + */ + private Function getSingleFunction() throws Exception { + return getSingleFunction(this.getFunctions()); + } + + /** + * Returns the only function defined in the given test file. + * + * @param fileNameWithoutExtension The name of the file declaring the function without a file extension. + * @return The only function defined in the test file. + */ + private Function getSingleFunction(String fileNameWithoutExtension) throws Exception { + return getSingleFunction(this.getFunctions(fileNameWithoutExtension)); + } + + /** + * Returns the only function contained in the given set of functions. + * + * @param functions The set of functions containing only one function. + * @return The sole function contained in the given set of functions. + */ + private static Function getSingleFunction(Set functions) { + assertNotNull(functions); + assertEquals(1, functions.size()); + Function function = functions.iterator().next(); + assertNotNull(function); + return function; + } + + /** + * Test transitive side-effects in different files. + */ + @Test + public void testPythonSideEffects8() throws Exception { + Function functionFromA = this.getSingleFunction("A"); + assertEquals("f", functionFromA.getIdentifier()); + + Function functionFromB = this.getSingleFunction("B"); + assertEquals("g", functionFromB.getIdentifier()); + + Set functionSet = new HashSet<>(Arrays.asList(functionFromA, functionFromB)); + testTransitivePythonSideEffects(functionSet); + } + + /** + * Like testPythonSideEffects but only a single call. Simplifies the call graph since there seems to be a node for each call to a + * function. + * + * @see HybridizeFunctionRefactoringTest#testPythonSideEffects + */ + @Test + public void testPythonSideEffects9() throws Exception { + Function function = getSingleFunction(); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); // the example uses a primitive type. + assertTrue("Expecting a Python side-effect.", function.getHasPythonSideEffects()); + } + + /** + * Test write(). + */ + @Test + public void testPythonSideEffects10() throws Exception { + Function function = getSingleFunction(); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // NOTE: Switch to asserTrue when https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/273 is fixed. + assertFalse("Not expecting a Python side-effect.", function.getHasPythonSideEffects()); + } + + /** + * Test writelines(). + */ + @Test + public void testPythonSideEffects11() throws Exception { + Function function = getSingleFunction(); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // NOTE: Switch to asserTrue when https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/273 is fixed. + assertFalse("Not expecting a Python side-effect.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects12() throws Exception { + Function function = getSingleFunction(); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // there's a Python statement with no side-effects. + assertFalse("This Python statement modifies a local variable.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects13() throws Exception { + Function function = getSingleFunction(); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // there's a Python statement with no side-effects. + assertFalse("This Python statement uses a list comprehension to modify a local variable.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects14() throws Exception { + Function function = getSingleFunction(); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // there's a Python statement with no side-effects. + assertFalse("This Python statement uses a lambda to modify a local variable.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects15() throws Exception { + Function function = getSingleFunction(); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // there's a Python statement with no side-effects. + assertFalse("This Python statement uses a loop to modify a local variable.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects16() throws Exception { + Function function = getFunction("f"); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // there's a Python statement with side-effects. + assertTrue("This Python statement uses a list comprehension to modify a global variable.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects17() throws Exception { + Function function = getFunction("f"); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // NOTE: Switch to assertTrue when https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/274 is fixed. + assertFalse("This Python statement uses a lambda to modify a global variable.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects18() throws Exception { + Function f = getFunction("f"); + assertFalse(f.isHybrid()); + assertFalse(f.getLikelyHasTensorParameter()); + assertTrue(f.getHasPythonSideEffects()); + + Function g = getFunction("g"); + assertTrue(g.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects19() throws Exception { + Function f = getFunction("f"); + assertFalse(f.isHybrid()); + assertFalse(f.getLikelyHasTensorParameter()); + assertFalse(f.getHasPythonSideEffects()); + + Function g = getFunction("g"); + assertFalse(g.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects20() throws Exception { + Function f = getFunction("f"); + assertFalse(f.isHybrid()); + assertFalse(f.getLikelyHasTensorParameter()); + assertTrue("Function f() calls g(), which has Python side-effets. Thus, f() also has Python side-effects.", + f.getHasPythonSideEffects()); + + Function g = getFunction("g"); + assertTrue("Function g() modifies a global variable through the global keyword.", g.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects21() throws Exception { + Function f = getFunction("f"); + assertFalse(f.isHybrid()); + assertFalse(f.getLikelyHasTensorParameter()); + assertFalse(f.getHasPythonSideEffects()); + + Function g = getFunction("g"); + assertFalse(g.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects22() throws Exception { + Set functionSet = getFunctions(); + + for (Function f : functionSet) { + assertFalse(f.isHybrid()); + assertFalse(f.getLikelyHasTensorParameter()); + assertFalse("This Python statement (transitively) uses a list comprehension to modify a local variable.", + f.getHasPythonSideEffects()); + } + } + + @Test + public void testPythonSideEffects23() throws Exception { + Set functionSet = getFunctions(); + + for (Function function : functionSet) { + switch (function.getIdentifier()) { + case "f": + case "g": + case "fun_with_side_effects": + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // there's a Python statement with side-effects. + assertTrue("This Python statement (transitively) uses a list comprehension to modify a global variable.", + function.getHasPythonSideEffects()); + break; + case "h": + assertFalse(function.getHasPythonSideEffects()); + break; + default: + throw new IllegalStateException("Unknown function: " + function + "."); + } + } + } + + @Test + public void testPythonSideEffects24() throws Exception { + Function function = getFunction("f"); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + assertFalse("This Python statement (transitively) uses a lambda to modify a local variable.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects25() throws Exception { + Function function = getFunction("f"); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // there's a Python statement with no side-effects. + assertFalse("This Python statement (transitively) uses a loop to modify a local variable.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects26() throws Exception { + Function function = getFunction("f"); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // there's a Python statement with side-effects. + assertTrue("A loop to modifies a global variable.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects27() throws Exception { + Function function = getFunction("f"); + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // there's a Python statement with side-effects. + assertTrue("A loop to modifies a global variable.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects28() throws Exception { + Map> map = this.getFunctions().stream() + .collect(Collectors.groupingBy(Function::getIdentifier, Collectors.toSet())); + + assertEquals(2, map.size()); + + map.get("f").stream().map(Function::getHasPythonSideEffects).forEach(s -> assertFalse(s)); + map.get("g").stream().map(Function::getHasPythonSideEffects).forEach(s -> assertTrue(s)); + } + + @Test + public void testPythonSideEffects29() throws Exception { + Function f = getFunction("f"); + assertTrue("Function f() calls g(), which has Python side-effets. Thus, f() also has Python side-effects.", + f.getHasPythonSideEffects()); + + Function g = getFunction("g"); + assertTrue("Function g() modifies a global variable through the global keyword.", g.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects30() throws Exception { + Function f = getFunction("f"); + assertFalse("Removed the global keyword from g().", f.getHasPythonSideEffects()); + + Function g = getFunction("g"); + assertFalse("Function g() modifies a lobal variable (removed the global keyword).", g.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects31() throws Exception { + Function f = getFunction("f"); + assertTrue(f.getHasPythonSideEffects()); + + Function g = getFunction("g"); + assertTrue(g.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects32() throws Exception { + Function f = getFunction("f"); + assertFalse(f.getHasPythonSideEffects()); + + Function g = getFunction("g"); + assertFalse(g.getHasPythonSideEffects()); + + Function h = this.getFunction("h"); + assertTrue(h.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects33() throws Exception { + Function g = getFunction("g"); + assertFalse("g() only returns the parameter.", g.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects34() throws Exception { + Function f = getFunction("f"); + assertFalse(f.getHasPythonSideEffects()); + + Function g = getFunction("g"); + assertFalse("g() modifies a copy of a parameter.", g.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects35() throws Exception { + Function function = getFunction("side_effect"); + + assertTrue(function.isHybrid()); + assertFalse("side_effect() is passed an integer (from docs).", function.getLikelyHasTensorParameter()); + assertTrue("side_effect() modifies a global list.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects36() throws Exception { + Function function = getFunction("side_effect"); + + assertTrue(function.isHybrid()); + assertFalse("side_effect() is passed an integer (from docs).", function.getLikelyHasTensorParameter()); + assertTrue("side_effect() modifies a global list.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects37() throws Exception { + Function function = getFunction("no_side_effect"); + + assertTrue(function.isHybrid()); + assertFalse("no_side_effect() is passed an integer (from docs).", function.getLikelyHasTensorParameter()); + assertFalse("no_side_effect() doesn't modifies a global list.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects38() throws Exception { + Function function = getFunction("Model.__call__"); + assertNotNull(function); + + assertTrue(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // Change to assertTrue() once https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/271 is fixed: + assertNull(function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects39() throws Exception { + Function function = getFunction("Model.__call__"); + assertNotNull(function); + + assertTrue(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + assertTrue(function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects40() throws Exception { + Function function = getFunction("buggy_consume_next"); + + assertTrue(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + // TODO: Change to assertTrue() when https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/278 is fixed: + assertFalse("next() moves the iterator's cursor, and the iterator is over a list.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects41() throws Exception { + Function function = getFunction("good_consume_next"); + + assertTrue(function.isHybrid()); + assertFalse("iterator still isn't a tensor. I wonder if you get speedup from that.", function.getLikelyHasTensorParameter()); + assertFalse("next() moves the iterator's cursor, but the iterator is over a dataset.", function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects42() throws Exception { + Function function = getFunction("leaky_function"); + + assertTrue(function.isHybrid()); + assertTrue(function.getLikelyHasTensorParameter()); + assertTrue(function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects43() throws Exception { + Function function = getFunction("leaky_function"); + + assertTrue(function.isHybrid()); + assertTrue(function.getLikelyHasTensorParameter()); + assertFalse(function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects44() throws Exception { + Function function = getFunction("leaky_function"); + + assertFalse(function.isHybrid()); + assertTrue(function.getLikelyHasTensorParameter()); + assertFalse(function.getHasPythonSideEffects()); + + assertTrue(function.getStatus().isOK()); + assertTrue(function.getRefactoring() == Refactoring.CONVERT_EAGER_FUNCTION_TO_HYBRID); + assertTrue(function.getPassingPrecondition() == PreconditionSuccess.P1); + assertEquals(Collections.singleton(Transformation.CONVERT_TO_HYBRID), function.getTransformations()); + } + + @Test + public void testPythonSideEffects45() throws Exception { + Function function = getFunction("leaky_function"); + + assertTrue(function.isHybrid()); + // This is a hybrid function, so the refactoring should be OPTIMIZE_HYBRID_FUNCTION. + assertEquals(Refactoring.OPTIMIZE_HYBRID_FUNCTION, function.getRefactoring()); + + assertTrue(function.getLikelyHasTensorParameter()); + // In table 2, we need it not to have a tensor parameter to de-hybridize, so this is a "failure." + assertTrue(function.getEntryMatchingFailure(PreconditionFailure.HAS_TENSOR_PARAMETERS).isError()); + + assertTrue(function.getHasPythonSideEffects()); + // We also can't de-hybridize if it has Python side-effects. So, that's an error. + assertTrue(function.getEntryMatchingFailure(PreconditionFailure.HAS_PYTHON_SIDE_EFFECTS).isError()); + // Also, we have a hybrid function with Python side-effects. Let's warn about that. + assertEquals(1, Arrays.stream(function.getStatus().getEntries()).map(RefactoringStatusEntry::getSeverity) + .filter(s -> s == RefactoringStatus.WARNING).count()); + + assertNull(function.getPassingPrecondition()); + assertTrue(function.getTransformations().isEmpty()); + } + + @Test + public void testPythonSideEffects46() throws Exception { + Function function = getFunction("leaky_function"); + + assertFalse(function.isHybrid()); + assertTrue(function.getLikelyHasTensorParameter()); + assertTrue(function.getHasPythonSideEffects()); + + RefactoringStatus status = function.getStatus(); + + // We have an eager function with a tensor parameter but Python side-effects. Should be a P1 failure. + assertFalse(status.isOK()); + assertEquals(PreconditionFailure.HAS_PYTHON_SIDE_EFFECTS.getCode(), status.getEntryWithHighestSeverity().getCode()); + assertEquals(Refactoring.CONVERT_EAGER_FUNCTION_TO_HYBRID, function.getRefactoring()); + assertNull(function.getPassingPrecondition()); + assertEquals(Collections.emptySet(), function.getTransformations()); + } + + @Test + public void testPythonSideEffects47() throws Exception { + Function leakyFunction = getFunction("leaky_function"); + + assertTrue(leakyFunction.isHybrid()); + assertTrue(leakyFunction.getLikelyHasTensorParameter()); + assertTrue(leakyFunction.getHasPythonSideEffects()); + + Function capturesLeakedTensor = getFunction("captures_leaked_tensor"); + + assertTrue(capturesLeakedTensor.isHybrid()); + assertTrue(capturesLeakedTensor.getLikelyHasTensorParameter()); + + // NOTE: This function doesn't have Python side-effects, but it does capture a "leaky" tensor. See + // https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/281. + assertFalse(capturesLeakedTensor.getHasPythonSideEffects()); + + assertFalse(capturesLeakedTensor.getStatus().isOK()); + assertTrue(capturesLeakedTensor.getStatus().hasError()); + assertFalse(capturesLeakedTensor.getStatus().hasFatalError()); + RefactoringStatusEntry error = capturesLeakedTensor.getStatus().getEntryMatchingSeverity(RefactoringStatus.ERROR); + assertEquals(PreconditionFailure.HAS_TENSOR_PARAMETERS.getCode(), error.getCode()); + + // NOTE: Change to assertEquals(..., 1, ...) once https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/281 is fixed. + assertEquals("We should warn that the hybrid function is capturing leaked tensors.", 0, + Arrays.stream(capturesLeakedTensor.getStatus().getEntries()).map(RefactoringStatusEntry::getSeverity) + .filter(s -> s == RefactoringStatus.WARNING).count()); + + assertNotNull(capturesLeakedTensor.getRefactoring()); + assertEquals("P2 \"failure.\"", Refactoring.OPTIMIZE_HYBRID_FUNCTION, capturesLeakedTensor.getRefactoring()); + assertNull(capturesLeakedTensor.getPassingPrecondition()); + assertTrue(capturesLeakedTensor.getTransformations().isEmpty()); + + long warningCount = Arrays.stream(capturesLeakedTensor.getStatus().getEntries()) + .filter(e -> e.getSeverity() == RefactoringStatus.WARNING).count(); + + // NOTE: Change to assertEquals(..., 1, ...) when https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/281 is fixed. + // NOTE: Add assertEquals(RefactoringStatus.WARNING, entry.getSeverity()) when + // https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/281 is fixed. + assertEquals("Warn about a hybrid function that leaks as a potential tensor.", 0, warningCount); + } + + @Test + public void testPythonSideEffects48() throws Exception { + Function leakyFunction = getFunction("leaky_function"); + + assertTrue(leakyFunction.isHybrid()); + assertTrue(leakyFunction.getLikelyHasTensorParameter()); + assertTrue(leakyFunction.getHasPythonSideEffects()); + + Function capturesLeakedTensor = getFunction("captures_leaked_tensor"); + + assertFalse(capturesLeakedTensor.isHybrid()); + assertTrue(capturesLeakedTensor.getLikelyHasTensorParameter()); + + // NOTE: This function doesn't have Python side-effects, but it does capture a "leaky" tensor. See + // https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/281. + assertFalse(capturesLeakedTensor.getHasPythonSideEffects()); + + // NOTE: Change to assertFalse once https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/281 is fixed. + assertTrue("Passes P1.", capturesLeakedTensor.getStatus().isOK()); + + assertFalse(capturesLeakedTensor.getStatus().hasWarning()); + // NOTE: Change to assertTrue once https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/281 is fixed. + assertFalse(capturesLeakedTensor.getStatus().hasError()); + + assertNotNull(capturesLeakedTensor.getRefactoring()); + assertEquals("We shouldn't refactor this but we do currently. Nevertheless, the refactoring kind should remain intact.", + Refactoring.CONVERT_EAGER_FUNCTION_TO_HYBRID, capturesLeakedTensor.getRefactoring()); + + // NOTE: Change to assertNull once https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/281 is fixed. + assertNotNull(capturesLeakedTensor.getPassingPrecondition()); + assertEquals("We really shouldn't refactor this.", capturesLeakedTensor.getPassingPrecondition(), PreconditionSuccess.P1); + + // NOTE: Change to assertTrue once https://github.com/ponder-lab/Hybridize-Functions-Refactoring/issues/281 is fixed. + assertFalse(capturesLeakedTensor.getTransformations().isEmpty()); + assertEquals("We really shouldn't transform this.", Collections.singleton(Transformation.CONVERT_TO_HYBRID), + capturesLeakedTensor.getTransformations()); + } + + @Test + public void testPythonSideEffects49() throws Exception { + Function function = getFunction("leaky_function"); + + assertFalse(function.isHybrid()); + assertTrue(function.getLikelyHasTensorParameter()); + assertTrue(function.getHasPythonSideEffects()); + + // This is a P1 failure. + RefactoringStatus status = function.getStatus(); + assertFalse(status.isOK()); + assertTrue(status.hasError()); + assertFalse(status.hasFatalError()); + + RefactoringStatusEntry entry = status.getEntryWithHighestSeverity(); + assertEquals(RefactoringStatus.ERROR, entry.getSeverity()); + assertEquals(PreconditionFailure.HAS_PYTHON_SIDE_EFFECTS.getCode(), entry.getCode()); + assertEquals(function, entry.getData()); + + assertEquals(Refactoring.CONVERT_EAGER_FUNCTION_TO_HYBRID, function.getRefactoring()); + assertNull(function.getPassingPrecondition()); + assertTrue(function.getTransformations().isEmpty()); + } + + @Test + public void testPythonSideEffects50() throws Exception { + Function function = getFunction("leaky_function"); + + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + assertTrue(function.getHasPythonSideEffects()); + + RefactoringStatus status = function.getStatus(); + assertFalse("This is a P1 failure.", status.isOK()); + assertTrue(status.hasError()); + assertFalse(status.hasFatalError()); + + RefactoringStatusEntry[] statusEntries = status.getEntries(); + assertEquals(2, statusEntries.length); + + assertTrue(Arrays.stream(statusEntries).map(RefactoringStatusEntry::getSeverity) + .allMatch(s -> Objects.equals(s, RefactoringStatus.ERROR))); + + assertTrue(Arrays.stream(statusEntries).map(RefactoringStatusEntry::getData).allMatch(d -> Objects.equals(d, function))); + + Map> codeToEntry = Arrays.stream(statusEntries) + .collect(Collectors.groupingBy(RefactoringStatusEntry::getCode)); + + assertEquals(1, codeToEntry.get(PreconditionFailure.HAS_NO_TENSOR_PARAMETERS.getCode()).size()); + assertEquals(1, codeToEntry.get(PreconditionFailure.HAS_PYTHON_SIDE_EFFECTS.getCode()).size()); + + assertEquals(Refactoring.CONVERT_EAGER_FUNCTION_TO_HYBRID, function.getRefactoring()); + assertNull(function.getPassingPrecondition()); + assertTrue(function.getTransformations().isEmpty()); + } + + @Test + public void testPythonSideEffects51() throws Exception { + Function function = getFunction("leaky_function"); + + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + assertTrue(function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects52() throws Exception { + Function function = getFunction("leaky_function"); + + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + assertTrue(function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects53() throws Exception { + Function function = getFunction("not_leaky_function"); + + assertFalse(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + assertFalse(function.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects54() throws Exception { + Function function = getFunction("leaky_function"); + + assertTrue(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + assertTrue(function.getHasPythonSideEffects()); + + RefactoringStatus status = function.getStatus(); + assertTrue("We can't convert something to eager if it has side-effects because that will alter semantics.", status.hasError()); + assertEquals(2, status.getEntries().length); + assertEquals(PreconditionFailure.HAS_PYTHON_SIDE_EFFECTS.getCode(), status.getEntryWithHighestSeverity().getCode()); + + assertEquals(Refactoring.OPTIMIZE_HYBRID_FUNCTION, function.getRefactoring()); + assertNull(function.getPassingPrecondition()); + assertTrue(function.getTransformations().isEmpty()); + } + + @Test + public void testPythonSideEffects55() throws Exception { + Function function = getFunction("leaky_function"); + + assertTrue(function.isHybrid()); + assertFalse(function.getLikelyHasTensorParameter()); + assertFalse(function.getHasPythonSideEffects()); + + RefactoringStatus status = function.getStatus(); + assertFalse("We can convert something to eager if it does not have side-effects because that will not alter semantics.", + status.hasError()); + assertEquals(0, status.getEntries().length); + + assertEquals(Refactoring.OPTIMIZE_HYBRID_FUNCTION, function.getRefactoring()); + assertNotNull(function.getPassingPrecondition()); + assertEquals(PreconditionSuccess.P2, function.getPassingPrecondition()); + assertFalse(function.getTransformations().isEmpty()); + assertEquals(Collections.singleton(Transformation.CONVERT_TO_EAGER), function.getTransformations()); + } + + @Test + public void testPythonSideEffects56() throws Exception { + Function f = getFunction("f"); + assertFalse("Keyword argument assignments shouldn't be considered as heap writes.", f.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects57() throws Exception { + Function f = getFunction("f"); + assertFalse("Embedded functions aren't side-effects.", f.getHasPythonSideEffects()); + } + + @Test + public void testPythonSideEffects58() throws Exception { + Function f = getFunction("f"); + assertFalse("Decorated embedded functions aren't side-effects.", f.getHasPythonSideEffects()); + } + + // TODO: Left off at: https://www.tensorflow.org/guide/function#recursive_tffunctions_are_not_supported } diff --git a/hybridize.target b/hybridize.target index 1169ab0bc..dd279f896 100644 --- a/hybridize.target +++ b/hybridize.target @@ -29,7 +29,7 @@ com.ibm.wala com.ibm.wala.cast.python.ml - 0.9.0-SNAPSHOT + 0.12.0-SNAPSHOT jar