fr.inria.coming.repairability.repairtools.Cardumen Maven / Gradle / Ivy
package fr.inria.coming.repairability.repairtools;
import fr.inria.coming.changeminer.analyzer.instancedetector.ChangePatternInstance;
import fr.inria.coming.changeminer.analyzer.patternspecification.ChangePatternSpecification;
import fr.inria.coming.changeminer.entity.IRevision;
import fr.inria.coming.changeminer.util.PatternXMLParser;
import fr.inria.coming.main.ComingProperties;
import fr.inria.coming.utils.ASTInfoResolver;
import fr.inria.coming.utils.CtEntityType;
import fr.inria.coming.utils.EntityTypesInfoResolver;
import gumtree.spoon.diff.Diff;
import gumtree.spoon.diff.operations.InsertOperation;
import gumtree.spoon.diff.operations.Operation;
import spoon.reflect.code.*;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtTypedElement;
import spoon.reflect.reference.CtTypeReference;
import java.util.*;
import java.util.stream.Collectors;
/**
* Cardumen fixes bugs by replacing expressions with template-based generated expressions.
* The templates of the generated expressions are extracted from the same file/package/project by replacing variables with
* placeholders.
*
* expression_replacement_by_upd.xml pattern collects the changes that update an expression.
* expression_replacement_by_del_ins.xml pattern collects the changes that delete an expression and then insert a new expression.
* expression_replacement_by_del_mov.xml pattern collects the changes that replace an expression with a part of it.
* expression_insertion_deep.xml pattern collects the changes that update an expression by inserting an expression to it.
*
* The filter functions should determine whether the new expression is an instance of a template from the same file.
*/
public class Cardumen extends AbstractRepairTool {
private static final String UPD_PATTERN_NAME = "expression_replacement_by_upd";
private static final String DEL_INS_PATTERN_NAME = "expression_replacement_by_del_ins";
private static final String DEL_MOV_PATTERN_NAME = "expression_replacement_by_del_mov";
private static final String[] patternFileNames = {
UPD_PATTERN_NAME + ".xml",
DEL_INS_PATTERN_NAME + ".xml",
DEL_MOV_PATTERN_NAME + ".xml"
};
/**
* Encodes the search space of JMutRepair
*
* @return a List of ChangePatternSpecifications that are supposed to be mined by PatternInstanceAnalyzer
*/
@Override
protected List readPatterns() {
List patterns = new ArrayList<>();
for (String fileName : patternFileNames) {
patterns.add(PatternXMLParser.parseFile(getPathFromResources(fileName)));
}
return patterns;
}
/**
* Certain patterns/characteristics of search-space of a repair tool can't be represented by ChangePatternSpecification
* This filter is supposed to delete/remove such instances from the results given by PatternInstanceAnalyser.
*
* The filter functions should determine whether the new expression is an instance of a template from the same file.
*
* @param instance
* @param diff
* @return
*/
@Override
public boolean filter(ChangePatternInstance instance, IRevision revision, Diff diff) {
CtElement srcNode = null, dstNode = null;
if (instance.getPattern().getName().contains(UPD_PATTERN_NAME)) {
Operation anyOperation = instance.getActions().get(0);
srcNode = anyOperation.getSrcNode();
dstNode = anyOperation.getDstNode();
} else if (instance.getPattern().getName().contains(DEL_INS_PATTERN_NAME)) {
Operation delAction, insAction;
delAction = getActionFromDelInstance(instance, "DEL");
insAction = getActionFromDelInstance(instance, "INS");
if (delAction.getSrcNode().getParent() != ((InsertOperation) insAction).getParent())
return false;
srcNode = delAction.getSrcNode();
dstNode = insAction.getSrcNode();
} else if (instance.getPattern().getName().contains(DEL_MOV_PATTERN_NAME)) {
if(!ComingProperties.getPropertyBoolean("exclude_repair_patterns_not_covering_the_whole_diff"))
// this pattern produces too many false positives when not-covering instances are not excluded.
return false;
srcNode = getActionFromDelInstance(instance, "DEL").getSrcNode();
dstNode = getActionFromDelInstance(instance, "MOV").getSrcNode();
return ASTInfoResolver.getPathToRootNode(dstNode).contains(srcNode);
} else {
return false;
}
return checkSrcIncludesDstTemplate(srcNode, dstNode);
}
private boolean checkSrcIncludesDstTemplate(CtElement srcNode, CtElement dstNode) {
CtElement srcRootNode = ASTInfoResolver.getPathToRootNode(srcNode).get(0);
List allSrcElements = srcRootNode.getElements(null);
Set srcVariablesAndLiterals = new HashSet<>();
for (int i = 0; i < allSrcElements.size(); i++) {
CtElement srcElement = allSrcElements.get(i);
if (srcElement instanceof CtVariableAccess || srcElement instanceof CtLiteral) {
srcVariablesAndLiterals.add(ASTInfoResolver.getCleanedName(srcElement));
}
if(srcElement instanceof CtField) {
srcVariablesAndLiterals.add(ASTInfoResolver.getCleanedName(((CtField)srcElement)
.getSimpleName()));
}
}
List allDstElements = dstNode.getElements(null);
String dstNodeAsString = dstNode.toString();
// the following for-loop replaces variable names/literals in dstNodeAsString with their type name
for (int i = 0; i < allDstElements.size(); i++) {
CtElement dstElement = allDstElements.get(i);
if (dstElement instanceof CtVariableAccess || dstElement instanceof CtLiteral) {
if (!srcVariablesAndLiterals.contains(ASTInfoResolver.getCleanedName(dstElement)))
// A variable/literal is used that does not exist in SRC
// FIXME: We should also make sure that the variable/literal is in the current scope
return false;
String variableOrLiteralType = getType(dstElement);
dstNodeAsString = replaceElement(dstNodeAsString, dstElement.toString(),
"#" + variableOrLiteralType + "#");
}
}
for (int i = 0; i < allSrcElements.size() - allDstElements.size() + 1; i++) {
CtElement currentSrcElement = allSrcElements.get(i);
String typeOfCurrentSrcElement = EntityTypesInfoResolver.getNodeLabelFromCtElement(currentSrcElement);
if (!EntityTypesInfoResolver.getInstance().isAChildOf(typeOfCurrentSrcElement, CtEntityType.EXPRESSION.toString())) {
continue;
}
String srcAsString = currentSrcElement.toString();
Set elementsInSubtree = new HashSet<>();
elementsInSubtree.add(currentSrcElement);
for (int j = 0; j == 0 ||
(i + j < allSrcElements.size() && elementsInSubtree.contains(allSrcElements.get(i + j).getParent()))
; j++) {
CtElement srcElement = allSrcElements.get(i + j);
elementsInSubtree.add(srcElement);
if (srcElement instanceof CtLiteral || srcElement instanceof CtVariableAccess) {
String variableOrLiteralType = getType(srcElement);
srcAsString = replaceElement(srcAsString, srcElement.toString(),
"#" + variableOrLiteralType + "#");
}
}
if (areTheSameTemplates(srcAsString, dstNodeAsString))
// the template of the dst-node is found in the src
return true;
}
return false;
}
private String replaceElement(String source, String element, String target) {
int fromInd = 0;
while (source.indexOf(element, fromInd) > -1) {
int ind = source.indexOf(element, fromInd);
if (!((ind > 0 && isVariableNameChar(source.charAt(ind - 1)))
|| (ind + element.length() < source.length() &&
isVariableNameChar(source.charAt(ind + element.length()))))) {
// the chars before and after the element are not a variable-name-char
source = source.substring(0, ind) + target + (ind + element.length() >= source.length() ? "" :
source.substring(ind + element.length()));
fromInd = ind + target.length();
if (fromInd >= source.length())
break;
continue;
}
fromInd = ind + element.length();
if (fromInd >= source.length())
break;
}
return source;
}
private boolean isVariableNameChar(char c) {
return (c <= 'z' && c >= 'a') || (c <= 'Z' && c >= 'A') || (c <= '9' && c >= '0');
}
private boolean areTheSameTemplates(String temp1, String temp2) {
temp1 = ASTInfoResolver.getCleanedName(temp1);
temp2 = ASTInfoResolver.getCleanedName(temp2);
String[] parts1 = temp1.split("#");
String[] parts2 = temp2.split("#");
if (parts1.length != parts2.length)
return false;
for (int i = 0; i < parts1.length; i++) {
if (!parts1[i].equals(parts2[i]) && !parts1[i].equals("") && !parts2[i].equals("")
&& !parts1[i].equals("null") && !parts2[i].equals("null"))
return false;
}
return true;
}
private String getType(CtElement element) {
CtTypeReference type = ((CtTypedElement) element).getType();
if (type == null)
return "";
return type.toString();
}
// DEL_MOV/INS might add instances that are already added by other patterns. They should be filtered.
@Override
public List filterSelectedInstances(List lst, Diff diff) {
Map instanceToCoveredNodes = new HashMap<>();
List ret = new ArrayList<>();
for (ChangePatternInstance instance : lst) {
if (instance.getPattern().getName().contains(UPD_PATTERN_NAME)) {
ret.add(instance);
instanceToCoveredNodes.put(instance, getInstanceCoveredNodes(instance, diff));
}
}
for (ChangePatternInstance instance : lst) {
if (instance.getPattern().getName().contains(DEL_MOV_PATTERN_NAME)
|| instance.getPattern().getName().contains(DEL_INS_PATTERN_NAME)) {
List changedNodes = new ArrayList<>();
changedNodes.add(getActionFromDelInstance(instance, "DEL").getSrcNode());
updateSelectedInstances(instanceToCoveredNodes, ret, instance, changedNodes, diff);
}
}
return ret;
}
private void updateSelectedInstances
(
Map instanceToCoveredNodes,
List ret,
ChangePatternInstance instance,
List changedNodes,
Diff diff
) {
boolean addedBefore = false;
for (ChangePatternInstance existingInstance : ret) {
Set instanceCoveredNodes = instanceToCoveredNodes.get(existingInstance);
for (CtElement changedNode : changedNodes) {
if (coveredByInstanceNodes(instanceCoveredNodes, changedNode)) {
addedBefore = true;
break;
}
}
if(addedBefore)
break;
}
if (!addedBefore) {
ret.add(instance);
instanceToCoveredNodes.put(instance, getInstanceCoveredNodes(instance, diff));
}
}
@Override
protected Set getInstanceCoveredNodes(ChangePatternInstance instance, Diff diff) {
Set dstNodes = new HashSet<>();
if (instance.getPattern().getName().contains(DEL_INS_PATTERN_NAME)) {
for (Operation op : instance.getActions()) {
if (op.getAction().getName().contains("INS")) {
dstNodes.add(op.getSrcNode());
}
}
} else if (instance.getPattern().getName().contains(UPD_PATTERN_NAME)
|| instance.getPattern().getName().contains(DEL_MOV_PATTERN_NAME)) {
dstNodes = instance.getActions().stream()
.map(action -> (action.getDstNode() != null ? action.getDstNode() : action.getSrcNode()))
.collect(Collectors.toSet());
}
Set srcNodes = new HashSet<>();
if (instance.getPattern().getName().contains(DEL_INS_PATTERN_NAME)
|| instance.getPattern().getName().contains(DEL_MOV_PATTERN_NAME)) {
for (Operation op : instance.getActions()) {
if (op.getAction().getName().contains("DEL")) {
srcNodes.add(op.getSrcNode());
}
}
} else if (instance.getPattern().getName().contains(UPD_PATTERN_NAME)) {
srcNodes = instance.getActions().stream()
.map(action -> (action.getSrcNode())).collect(Collectors.toSet());
}
Set all = dstNodes;
all.addAll(srcNodes);
return all;
}
private Operation getActionFromDelInstance(ChangePatternInstance instance, String actionType) {
if (instance.getActions().get(0).getAction().getName().equals(actionType)) {
return instance.getActions().get(0);
} else {
return instance.getActions().get(1);
}
}
}