From fa2ecd5b4b78d1343ef6d9437ebf5e59ba3f6dcf Mon Sep 17 00:00:00 2001 From: Lasse Lindqvist Date: Thu, 8 Nov 2018 22:10:49 +0200 Subject: [PATCH] Implement issue #705 Implement new detector IRA_INEFFICIENT_REPLACEALL. Finds occurrences of replaceAll(String regex, String replacement) without any special regex characters. --- .../edu/umd/cs/findbugs/ba/Issue705Test.java | 42 +++++ spotbugs/etc/findbugs.xml | 4 + spotbugs/etc/messages.xml | 20 +++ .../detect/InefficientReplaceAll.java | 149 ++++++++++++++++++ .../src/java/ghIssues/Issue705.java | 25 +++ 5 files changed, 240 insertions(+) create mode 100644 spotbugs-tests/src/test/java/edu/umd/cs/findbugs/ba/Issue705Test.java create mode 100644 spotbugs/src/main/java/edu/umd/cs/findbugs/detect/InefficientReplaceAll.java create mode 100644 spotbugsTestCases/src/java/ghIssues/Issue705.java diff --git a/spotbugs-tests/src/test/java/edu/umd/cs/findbugs/ba/Issue705Test.java b/spotbugs-tests/src/test/java/edu/umd/cs/findbugs/ba/Issue705Test.java new file mode 100644 index 00000000000..444c11b91e5 --- /dev/null +++ b/spotbugs-tests/src/test/java/edu/umd/cs/findbugs/ba/Issue705Test.java @@ -0,0 +1,42 @@ +/* + * Contributions to SpotBugs + * Copyright (C) 2018, kengo + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package edu.umd.cs.findbugs.ba; + +import static edu.umd.cs.findbugs.test.CountMatcher.containsExactly; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import edu.umd.cs.findbugs.AbstractIntegrationTest; +import edu.umd.cs.findbugs.SortedBugCollection; +import edu.umd.cs.findbugs.test.matcher.BugInstanceMatcher; +import edu.umd.cs.findbugs.test.matcher.BugInstanceMatcherBuilder; + +public class Issue705Test extends AbstractIntegrationTest { + + @Test + public void test() { + performAnalysis("ghIssues/Issue705.class"); + final BugInstanceMatcher bugTypeMatcher = new BugInstanceMatcherBuilder() + .bugType("IRA_INEFFICIENT_REPLACEALL").build(); + SortedBugCollection bugCollection = (SortedBugCollection) getBugCollection(); + assertThat(bugCollection, containsExactly(1, bugTypeMatcher)); + } + +} diff --git a/spotbugs/etc/findbugs.xml b/spotbugs/etc/findbugs.xml index 379674b7d65..bdb8c9b2854 100644 --- a/spotbugs/etc/findbugs.xml +++ b/spotbugs/etc/findbugs.xml @@ -524,6 +524,8 @@ reports="IIO_INEFFICIENT_INDEX_OF,IIO_INEFFICIENT_LAST_INDEX_OF"/> + + diff --git a/spotbugs/etc/messages.xml b/spotbugs/etc/messages.xml index ef00d7a374b..5351fec6a9c 100644 --- a/spotbugs/etc/messages.xml +++ b/spotbugs/etc/messages.xml @@ -1156,6 +1156,14 @@ A fast detector. using the toArray() method that takes a prototype array, passing an array argument which is zero-length.

+]]> + +
+ +
+ This detector finds occurrences of replaceAll(String regex, String replacement) without any special regex characters. +

]]>
@@ -6407,6 +6415,18 @@ If the array passed in is big enough to store all of the elements of the collection, then it is populated and returned directly. This avoids the need to create a second array (by reflection) to return as the result.

+]]> + + + + Method uses replaceAll(String regex, String replacement) without any special regex characters + {1} uses replaceAll(String regex, String replacement) without any special regex characters +
+ This method uses replaceAll(String regex, String replacement) without any special regex characters. +It is more efficient to use +myString.replace(CharSequence target, CharSequence replacement) +if there is no need to use regular expressions.

]]>
diff --git a/spotbugs/src/main/java/edu/umd/cs/findbugs/detect/InefficientReplaceAll.java b/spotbugs/src/main/java/edu/umd/cs/findbugs/detect/InefficientReplaceAll.java new file mode 100644 index 00000000000..48d1f6da4db --- /dev/null +++ b/spotbugs/src/main/java/edu/umd/cs/findbugs/detect/InefficientReplaceAll.java @@ -0,0 +1,149 @@ +/* + * FindBugs - Find bugs in Java programs + * Copyright (C) 2004 Dave Brosius + * Copyright (C) 2004 University of Maryland + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package edu.umd.cs.findbugs.detect; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.bcel.Const; +import org.apache.bcel.classfile.Code; +import org.apache.bcel.classfile.Method; + +import edu.umd.cs.findbugs.BugAccumulator; +import edu.umd.cs.findbugs.BugInstance; +import edu.umd.cs.findbugs.BugReporter; +import edu.umd.cs.findbugs.StatelessDetector; +import edu.umd.cs.findbugs.SystemProperties; +import edu.umd.cs.findbugs.ba.ClassContext; +import edu.umd.cs.findbugs.bcel.OpcodeStackDetector; +import edu.umd.cs.findbugs.classfile.MethodDescriptor; + +/** + * Find occurrences of replaceAll(String regex, String replacement) without any special regex characters. + * + * @author Lasse Lindqvist + */ +public class InefficientReplaceAll extends OpcodeStackDetector implements StatelessDetector { + private static final boolean DEBUG = SystemProperties.getBoolean("ira.debug"); + + private static final List methods = Collections.singletonList(new MethodDescriptor("", "replaceAll", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")); + + private final BugReporter bugReporter; + + private final BugAccumulator bugAccumulator; + + private static List regexCharList = new ArrayList<>(); + static { + regexCharList.add("."); + regexCharList.add("\\"); + regexCharList.add("["); + regexCharList.add("]"); + regexCharList.add("{"); + regexCharList.add("}"); + regexCharList.add("("); + regexCharList.add(")"); + regexCharList.add("<"); + regexCharList.add(">"); + regexCharList.add("*"); + regexCharList.add("+"); + regexCharList.add("-"); + regexCharList.add("="); + regexCharList.add("?"); + regexCharList.add("^"); + regexCharList.add("$"); + regexCharList.add("|"); + } + + public InefficientReplaceAll(BugReporter bugReporter) { + this.bugReporter = bugReporter; + this.bugAccumulator = new BugAccumulator(bugReporter); + } + + @Override + public void visitClassContext(ClassContext classContext) { + if (hasInterestingMethod(classContext.getJavaClass().getConstantPool(), methods)) { + classContext.getJavaClass().accept(this); + } + } + + @Override + public void visit(Method obj) { + if (DEBUG) { + System.out.println("------------------- Analyzing " + obj.getName() + " ----------------"); + } + super.visit(obj); + } + + @Override + public void visit(Code obj) { + super.visit(obj); + bugAccumulator.reportAccumulatedBugs(); + + } + + @Override + public void sawOpcode(int seen) { + if (DEBUG) { + System.out.println("Opcode: " + Const.getOpcodeName(seen)); + } + if (((seen == Const.INVOKEVIRTUAL) || (seen == Const.INVOKEINTERFACE)) && ("replaceAll".equals(getNameConstantOperand())) + && ("(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;".equals(getSigConstantOperand())) + && hasConstantArguments()) { + String firstArgument = getFirstArgument(); + boolean found = false; + for (String regexChar : regexCharList) { + if (firstArgument.contains(String.valueOf(regexChar))) { + found = true; + break; + } + } + if (!found) { + bugAccumulator.accumulateBug( + new BugInstance(this, "IRA_INEFFICIENT_REPLACEALL", LOW_PRIORITY).addClassAndMethod(this), this); + } + } + } + + /** + * @return first argument of the called method if it's a constant + */ + private String getFirstArgument() { + Object value = getStack().getStackItem(getNumberArguments(getMethodDescriptorOperand().getSignature()) - 1) + .getConstant(); + return value == null ? null : value.toString(); + } + + /** + * @return true if only constants are passed to the called method + */ + private boolean hasConstantArguments() { + int nArgs = getNumberArguments(getMethodDescriptorOperand().getSignature()); + for (int i = 0; i < nArgs; i++) { + if (getStack().getStackItem(i).getConstant() == null) { + return false; + } + } + return true; + } +} + diff --git a/spotbugsTestCases/src/java/ghIssues/Issue705.java b/spotbugsTestCases/src/java/ghIssues/Issue705.java new file mode 100644 index 00000000000..5f263e8bc8a --- /dev/null +++ b/spotbugsTestCases/src/java/ghIssues/Issue705.java @@ -0,0 +1,25 @@ +package ghIssues; + +public class Issue705 { + + public static String bug() { + String replaceable = "replaceable"; + String replacement = "replacement"; + String target = "target"; + return target.replaceAll(replaceable, replacement); + } + + public static String nonBug() { + String replaceable = "(replaceable)"; + String replacement = "replacement"; + String target = "target"; + return target.replaceAll(replaceable, replacement); + } + + public static String nonBug2() { + String replaceable = "(.*replaceable)"; + String replacement = "replacement"; + String target = "target"; + return target.replaceAll(replaceable, replacement); + } +}