Skip to content Skip to sidebar Skip to footer

Android Bug? : String.substring(5).replace(“”, “”) // Empty String

Here is my code: String str = 'just_a_string'; System.out.println(']' + str + '['); System.out.println(']' + str.replace('', '') + '['); System.out.println(']' + str.substring(5) +

Solution 1:

Bingo! i have found the bug!

Thanks @izht for providing the link of the source code. i have located the bug regarding this problem.

This only happens when the String's backing array is having a different (longer) value than the actual String. In particular, when the String.offset (private variable) is larger than zero.

Here's the fix:

public String replace(CharSequence target, CharSequence replacement) {
    if (target == null) {
        thrownewNullPointerException("target == null");
    }
    if (replacement == null) {
        thrownewNullPointerException("replacement == null");
    }

    StringtargetString= target.toString();
    intmatchStart= indexOf(targetString, 0);
    if (matchStart == -1) {
        // If there's nothing to replace, return the original string untouched.returnthis;
    }

    StringreplacementString= replacement.toString();

    // The empty target matches at the start and end and between each character.inttargetLength= targetString.length();
    if (targetLength == 0) {
        intresultLength= (count + 2) * replacementString.length();
        StringBuilderresult=newStringBuilder(resultLength);
        result.append(replacementString);
//        for (int i = offset; i < count; ++i) {             // original, bugfor (inti= offset; i < (count + offset); ++i) {    // fix
            result.append(value[i]);
            result.append(replacementString);
        }
        return result.toString();
    }

    StringBuilderresult=newStringBuilder(count);
    intsearchStart=0;
    do {
        // Copy characters before the match...
        result.append(value, offset + searchStart, matchStart - searchStart);
        // Insert the replacement...
        result.append(replacementString);
        // And skip over the match...
        searchStart = matchStart + targetLength;
    } while ((matchStart = indexOf(targetString, searchStart)) != -1);
    // Copy any trailing chars...
    result.append(value, offset + searchStart, count - searchStart);
    return result.toString();
}

i am not sure why Android has to alter (and altered wrongly) the replace() in this way. The original Java implementation doesn't have this issue.

By-the-way, what's now? What can i do with it? (other than using replace() with extra care, or throw away my Android phones :-/)


Btw i m quite sure my LG E720 Optimus Chic (Android 2.2) is using a different source code than that one. It keeps halting (suspect infinite looping) upon String.replace() with an empty target string. Lately i found it throws this error message:

05-1016:41:13.155: E/AndroidRuntime(9384): FATAL EXCEPTION: main
05-1016:41:13.155: E/AndroidRuntime(9384): java.lang.OutOfMemoryError
05-1016:41:13.155: E/AndroidRuntime(9384):   at java.lang.AbstractStringBuilder.enlargeBuffer(AbstractStringBuilder.java:97)
05-1016:41:13.155: E/AndroidRuntime(9384):   at java.lang.AbstractStringBuilder.append0(AbstractStringBuilder.java:157)
05-1016:41:13.155: E/AndroidRuntime(9384):   at java.lang.StringBuilder.append(StringBuilder.java:217)
05-1016:41:13.155: E/AndroidRuntime(9384):   at java.lang.String.replace(String.java:1497)
05-1016:41:13.155: E/AndroidRuntime(9384):   at com.example.testprojectnew.MainActivity.onCreate(MainActivity.java:22)
05-1016:41:13.155: E/AndroidRuntime(9384):   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
05-1016:41:13.155: E/AndroidRuntime(9384):   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)
05-1016:41:13.155: E/AndroidRuntime(9384):   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679)
05-1016:41:13.155: E/AndroidRuntime(9384):   at android.app.ActivityThread.access$2300(ActivityThread.java:125)
05-1016:41:13.155: E/AndroidRuntime(9384):   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2033)
05-1016:41:13.155: E/AndroidRuntime(9384):   at android.os.Handler.dispatchMessage(Handler.java:99)
05-1016:41:13.155: E/AndroidRuntime(9384):   at android.os.Looper.loop(Looper.java:123)
05-1016:41:13.155: E/AndroidRuntime(9384):   at android.app.ActivityThread.main(ActivityThread.java:4627)
05-1016:41:13.155: E/AndroidRuntime(9384):   at java.lang.reflect.Method.invokeNative(Native Method)
05-1016:41:13.155: E/AndroidRuntime(9384):   at java.lang.reflect.Method.invoke(Method.java:521)
05-1016:41:13.155: E/AndroidRuntime(9384):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:878)
05-1016:41:13.155: E/AndroidRuntime(9384):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:636)
05-1016:41:13.155: E/AndroidRuntime(9384):   at dalvik.system.NativeStart.main(Native Method)

At a second thought, if that for-loop thingy is the bug. It should be a compile time issue. Why would it act differently in different phones (different versions of Android)?


Complete Workaround

Got an update from Google, that they have patched it, and will correct it in the future release.

Meanwhile, i have written a patched method, based on their code:

(This is necessary because (1) we still have to wait for the correct release, (2) we need to take care of devices that didnt make that fixed update)

/** Patch for the String.replace(CharSequence target, CharSequence replacement),
 *  because the original is buggy when CharSequence target is empty, i.e. "".
 *  Patched by Google Android: https://android-review.googlesource.com/58393
 */publicstaticString replacePatched(finalStringstring, final CharSequence target, final CharSequence replacement) {
    if (target == null) {
        thrownew NullPointerException("target == null");
    }
    if (replacement == null) {
        thrownew NullPointerException("replacement == null");
    }

    finalString targetString = target.toString();
    int matchStart = string.indexOf(targetString, 0);
    if (matchStart == -1) {
        // If there's nothing to replace, return the original string untouched.returnnewString(string);
    }

    final char[] value = string.toCharArray();                              // required in patchfinalint count = value.length;                                         // required in patchfinalString replacementString = replacement.toString();

    // The empty target matches at the start and end and between each character.if (targetString.length() == 0) {
        // The result contains the original 'count' characters, a copy of the// replacement string before every one of those characters, and a final// copy of the replacement string at the end.final StringBuilder result = new StringBuilder(count + (count + 1) * replacementString.length());
        result.append(replacementString);
        for (int i = 0; i < count; ++i) {
            result.append(value[i]);
            result.append(replacementString);
        }
        returnnewString(result);      // StringBuilder.toString() does not give exact length
    }

    final StringBuilder result = new StringBuilder(count);
    int searchStart = 0;
    do {
        // Copy characters before the match...
        result.append(value, searchStart, matchStart - searchStart);
        // Insert the replacement...
        result.append(replacementString);
        // And skip over the match...
        searchStart = matchStart + targetString.length();
    } while ((matchStart = string.indexOf(targetString, searchStart)) != -1);
    // Copy any trailing chars...
    result.append(value, searchStart, count - searchStart);
    returnnewString(result);          // StringBuilder.toString() does not give exact length
}

The verbose version:

/** Patch for the String.replace(CharSequence target, CharSequence replacement),
 *  because the original is buggy when CharSequence target is empty, i.e. "".
 *  Patched by Google Android: https://android-review.googlesource.com/58393
 */publicstaticString replacePatched(finalStringstring, final CharSequence target, final CharSequence replacement) {
    if (target == null) {
        thrownew NullPointerException("target == null");
    }
    if (replacement == null) {
        thrownew NullPointerException("replacement == null");
    }

//    String targetString = target.toString();                                    // originalfinalString targetString = target.toString();
//    int matchStart = indexOf(targetString, 0);                                  // originalint matchStart = string.indexOf(targetString, 0);
    if (matchStart == -1) {
        // If there's nothing to replace, return the original string untouched.//        return this;                                                            // originalreturnnewString(string);
    }

    final char[] value = string.toCharArray();                              // required in patchfinalint count = value.length;                                         // required in patch//    String replacementString = replacement.toString();                          // originalfinalString replacementString = replacement.toString();

    // The empty target matches at the start and end and between each character.//    int targetLength = targetString.length();                                   // original//    if (targetLength == 0) {                                                    // originalif (targetString.length() == 0) {
//        int resultLength = (count + 2) * replacementString.length();            // original//        // The result contains the original 'count' characters, a copy of the//        // replacement string before every one of those characters, and a final//        // copy of the replacement string at the end.//        int resultLength = count + (count + 1) * replacementString.length();    // patched by Google Android//        StringBuilder result = new StringBuilder(resultLength);                 // originalfinal StringBuilder result = new StringBuilder(count + (count + 1) * replacementString.length());
        result.append(replacementString);
//        for (int i = offset; i < count; ++i) {                                  // original//        int end = offset + count;                                               // patched by Google Android//        for (int i = offset; i != end; ++i) {                                   // patched by Google Androidfor (int i = 0; i < count; ++i) {
            result.append(value[i]);
            result.append(replacementString);
        }
//        return result.toString();                                               // originalreturnnewString(result);      // StringBuilder.toString() does not give exact length
    }

//    StringBuilder result = new StringBuilder(count);                            // originalfinal StringBuilder result = new StringBuilder(count);
    int searchStart = 0;
    do {
        // Copy characters before the match...//        result.append(value, offset + searchStart, matchStart - searchStart);   // original
        result.append(value, searchStart, matchStart - searchStart);
        // Insert the replacement...
        result.append(replacementString);
        // And skip over the match...//        searchStart = matchStart + targetLength;                                // original
        searchStart = matchStart + targetString.length();
//    } while ((matchStart = indexOf(targetString, searchStart)) != -1);          // original
    } while ((matchStart = string.indexOf(targetString, searchStart)) != -1);
    // Copy any trailing chars...//    result.append(value, offset + searchStart, count - searchStart);            // original
    result.append(value, searchStart, count - searchStart);
//    return result.toString();                                                   // originalreturnnewString(result);          // StringBuilder.toString() does not give exact length
}

Post a Comment for "Android Bug? : String.substring(5).replace(“”, “”) // Empty String"