package quickfix; import static edu.umd.cs.findbugs.plugin.eclipse.quickfix.util.ASTUtil.getASTNode; import static util.TraversalUtil.backtrackToBlock; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import edu.umd.cs.findbugs.BugInstance; import edu.umd.cs.findbugs.plugin.eclipse.quickfix.BugResolution; import edu.umd.cs.findbugs.plugin.eclipse.quickfix.exception.BugResolutionException; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; public class LiteralStringComparisonResolution extends BugResolution { @Override protected boolean resolveBindings() { return true; // we want the type to make sure the receiver of the .equals() is a String } @Override protected void repairBug(ASTRewrite rewrite, CompilationUnit workingUnit, BugInstance bug) throws BugResolutionException { ASTNode node = getASTNode(workingUnit, bug.getPrimarySourceLineAnnotation()); node = backtrackToBlock(node); LSCVisitor lscFinder = new LSCVisitor(); node.accept(lscFinder); for (ResolutionBundle toResolve : lscFinder.problemsToResolve) { MethodInvocation badMethodInvocation = toResolve.lscMethodInvocation; MethodInvocation fixedMethodInvocation = createFixedMethodInvocation(rewrite, toResolve); rewrite.replace(badMethodInvocation, fixedMethodInvocation, null); } } @SuppressWarnings("unchecked") private MethodInvocation createFixedMethodInvocation(ASTRewrite rewrite, ResolutionBundle toResolve) { AST ast = rewrite.getAST(); MethodInvocation fixedMethodInvocation = ast.newMethodInvocation(); String invokedMethodName = toResolve.lscMethodInvocation.getName().getIdentifier(); fixedMethodInvocation.setName(ast.newSimpleName(invokedMethodName)); // can't simply use visitor.stringLiteralExpression because an IllegalArgumentException // will be thrown because it belongs to another AST. So, we use a moveTarget to eventually // move the literal into the right place fixedMethodInvocation.setExpression((Expression) rewrite.createMoveTarget(toResolve.stringLiteralExpression)); // thing the method is called on fixedMethodInvocation.arguments().add((Expression) rewrite.createMoveTarget(toResolve.stringVariableExpression)); return fixedMethodInvocation; } private static class ResolutionBundle { public MethodInvocation lscMethodInvocation; public Expression stringLiteralExpression; public Expression stringVariableExpression; public ResolutionBundle(MethodInvocation lscMethodInvocation, Expression stringLiteralExpression, Expression stringVariableExpression) { this.lscMethodInvocation = lscMethodInvocation; this.stringLiteralExpression = stringLiteralExpression; this.stringVariableExpression = stringVariableExpression; } } private static class LSCVisitor extends ASTVisitor { private static Set<String> comparisonMethods = new HashSet<String>(3); static { comparisonMethods.add("equals"); comparisonMethods.add("compareTo"); comparisonMethods.add("equalsIgnoreCase"); } List<ResolutionBundle> problemsToResolve = new ArrayList<>(); @Override @SuppressWarnings("unchecked") public boolean visit(MethodInvocation node) { // for checking the type of the receiver. Although it is tempting to try // node.resolveTypeBinding(), that refers to the return value. Expression expression = node.getExpression(); if (expression != null) { ITypeBinding typeBinding = expression.resolveTypeBinding(); if (comparisonMethods.contains(node.getName().getIdentifier()) && // check the method name "java.lang.String".equals(typeBinding.getQualifiedName())) { List<Expression> arguments = (List<Expression>) node.arguments(); if (arguments.size() == 1) { // Sanity check to make sure this isn't a look alike Expression argument = arguments.get(0); if (argument.resolveConstantExpressionValue() != null) { // if this was a constant string, resolveConstantExpressionValue() will be nonnull // We can't simply do argument instanceof StringLiteral because if we have Class.CONSTANT, // that isn't a StringLiteral problemsToResolve.add(new ResolutionBundle(node, argument, expression)); } } } } return true; } } }