You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by mb...@apache.org on 2012/01/23 18:47:22 UTC
svn commit: r1234915 - in /commons/proper/lang/trunk: pom.xml
src/main/java/org/apache/commons/lang3/StringUtils.java
src/test/java/org/apache/commons/lang3/StringUtilsEqualsIndexOfTest.java
Author: mbenson
Date: Mon Jan 23 17:47:21 2012
New Revision: 1234915
URL: http://svn.apache.org/viewvc?rev=1234915&view=rev
Log:
[LANG-786] StringUtils equals() relies on undefined behavior; thanks to Daniel Trebbien
Modified:
commons/proper/lang/trunk/pom.xml
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/StringUtils.java
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/StringUtilsEqualsIndexOfTest.java
Modified: commons/proper/lang/trunk/pom.xml
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/pom.xml?rev=1234915&r1=1234914&r2=1234915&view=diff
==============================================================================
--- commons/proper/lang/trunk/pom.xml (original)
+++ commons/proper/lang/trunk/pom.xml Mon Jan 23 17:47:21 2012
@@ -395,6 +395,9 @@
<name>Masato Tezuka</name>
</contributor>
<contributor>
+ <name>Daniel Trebbien</name>
+ </contributor>
+ <contributor>
<name>Jeff Varszegi</name>
</contributor>
<contributor>
Modified: commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/StringUtils.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/StringUtils.java?rev=1234915&r1=1234914&r2=1234915&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/StringUtils.java (original)
+++ commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/StringUtils.java Mon Jan 23 17:47:21 2012
@@ -758,7 +758,8 @@ public class StringUtils {
// Equals
//-----------------------------------------------------------------------
/**
- * <p>Compares two CharSequences, returning {@code true} if they are equal.</p>
+ * <p>Compares two CharSequences, returning {@code true} if they represent
+ * equal sequences of characters.</p>
*
* <p>{@code null}s are handled without exceptions. Two {@code null}
* references are considered to be equal. The comparison is case sensitive.</p>
@@ -771,20 +772,28 @@ public class StringUtils {
* StringUtils.equals("abc", "ABC") = false
* </pre>
*
- * @see java.lang.String#equals(Object)
- * @param cs1 the first CharSequence, may be null
- * @param cs2 the second CharSequence, may be null
- * @return {@code true} if the CharSequences are equal, case sensitive, or
- * both {@code null}
+ * @see java.lang.CharSequence#equals(Object)
+ * @param cs1 the first CharSequence, may be {@code null}
+ * @param cs2 the second CharSequence, may be {@code null}
+ * @return {@code true} if the CharSequences are equal (case-sensitive), or both {@code null}
* @since 3.0 Changed signature from equals(String, String) to equals(CharSequence, CharSequence)
*/
public static boolean equals(CharSequence cs1, CharSequence cs2) {
- return cs1 == null ? cs2 == null : cs1.equals(cs2);
+ if (cs1 == cs2) {
+ return true;
+ }
+ if (cs1 == null || cs2 == null) {
+ return false;
+ }
+ if (cs1 instanceof String && cs2 instanceof String) {
+ return cs1.equals(cs2);
+ }
+ return CharSequenceUtils.regionMatches(cs1, false, 0, cs2, 0, Math.max(cs1.length(), cs2.length()));
}
/**
- * <p>Compares two CharSequences, returning {@code true} if they are equal ignoring
- * the case.</p>
+ * <p>Compares two CharSequences, returning {@code true} if they represent
+ * equal sequences of characters, ignoring case.</p>
*
* <p>{@code null}s are handled without exceptions. Two {@code null}
* references are considered equal. Comparison is case insensitive.</p>
Modified: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/StringUtilsEqualsIndexOfTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/StringUtilsEqualsIndexOfTest.java?rev=1234915&r1=1234914&r2=1234915&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/StringUtilsEqualsIndexOfTest.java (original)
+++ commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/StringUtilsEqualsIndexOfTest.java Mon Jan 23 17:47:21 2012
@@ -19,6 +19,8 @@ package org.apache.commons.lang3;
import java.util.Locale;
import junit.framework.TestCase;
+import org.hamcrest.core.IsNot;
+import static org.junit.Assert.assertThat;
/**
* Unit tests {@link org.apache.commons.lang3.StringUtils} - Substring methods
@@ -438,14 +440,75 @@ public class StringUtilsEqualsIndexOfTes
assertTrue( StringUtils.containsWhitespace("\n") );
}
+ // The purpose of this class is to test StringUtils#equals(CharSequence, CharSequence)
+ // with a CharSequence implementation whose equals(Object) override requires that the
+ // other object be an instance of CustomCharSequence, even though, as char sequences,
+ // `seq` may equal the other object.
+ private static class CustomCharSequence implements CharSequence {
+ private CharSequence seq;
+
+ public CustomCharSequence(CharSequence seq) {
+ this.seq = seq;
+ }
+
+ public char charAt(int index) {
+ return seq.charAt(index);
+ }
+
+ public int length() {
+ return seq.length();
+ }
+
+ public CharSequence subSequence(int start, int end) {
+ return new CustomCharSequence(seq.subSequence(start, end));
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj instanceof CustomCharSequence)) {
+ return false;
+ }
+ CustomCharSequence other = (CustomCharSequence) obj;
+ return seq.equals(other.seq);
+ }
+
+ public String toString() {
+ return seq.toString();
+ }
+ }
+
+ public void testCustomCharSequence() {
+ assertThat((CharSequence) new CustomCharSequence(FOO), IsNot.<CharSequence>not(FOO));
+ assertThat((CharSequence) FOO, IsNot.<CharSequence>not(new CustomCharSequence(FOO)));
+ assertEquals(new CustomCharSequence(FOO), new CustomCharSequence(FOO));
+ }
+
public void testEquals() {
- assertEquals(true, StringUtils.equals(null, null));
- assertEquals(true, StringUtils.equals(FOO, FOO));
- assertEquals(true, StringUtils.equals(FOO, new String(new char[] { 'f', 'o', 'o' })));
- assertEquals(false, StringUtils.equals(FOO, new String(new char[] { 'f', 'O', 'O' })));
- assertEquals(false, StringUtils.equals(FOO, BAR));
- assertEquals(false, StringUtils.equals(FOO, null));
- assertEquals(false, StringUtils.equals(null, FOO));
+ final CharSequence fooCs = FOO, barCs = BAR, foobarCs = FOOBAR;
+ assertTrue(StringUtils.equals(null, null));
+ assertTrue(StringUtils.equals(fooCs, fooCs));
+ assertTrue(StringUtils.equals(fooCs, (CharSequence) new StringBuilder(FOO)));
+ assertTrue(StringUtils.equals(fooCs, (CharSequence) new String(new char[] { 'f', 'o', 'o' })));
+ assertTrue(StringUtils.equals(fooCs, (CharSequence) new CustomCharSequence(FOO)));
+ assertTrue(StringUtils.equals((CharSequence) new CustomCharSequence(FOO), fooCs));
+ assertFalse(StringUtils.equals(fooCs, (CharSequence) new String(new char[] { 'f', 'O', 'O' })));
+ assertFalse(StringUtils.equals(fooCs, barCs));
+ assertFalse(StringUtils.equals(fooCs, null));
+ assertFalse(StringUtils.equals(null, fooCs));
+ assertFalse(StringUtils.equals(fooCs, foobarCs));
+ assertFalse(StringUtils.equals(foobarCs, fooCs));
+ }
+
+ public void testEqualsOnStrings() {
+ assertTrue(StringUtils.equals(null, null));
+ assertTrue(StringUtils.equals(FOO, FOO));
+ assertTrue(StringUtils.equals(FOO, new String(new char[] { 'f', 'o', 'o' })));
+ assertFalse(StringUtils.equals(FOO, new String(new char[] { 'f', 'O', 'O' })));
+ assertFalse(StringUtils.equals(FOO, BAR));
+ assertFalse(StringUtils.equals(FOO, null));
+ assertFalse(StringUtils.equals(null, FOO));
+ assertFalse(StringUtils.equals(FOO, FOOBAR));
+ assertFalse(StringUtils.equals(FOOBAR, FOO));
}
public void testEqualsIgnoreCase() {