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() {