0

While auditing an android source code, I found a string comparison bug which used == instead of equals(). However, the app is working well surprisingly!

After some testing, I found that replaceAll() method is hiding the bug.

String description = " ";
description = description.trim();
Result1.setText(description + " == " + "" + ": " + (description == ""));

prints "==:false" as I expected. However,

String description = " ";
description = description.trim().replaceAll("\\s+|\\r+|\\n+", " ");
Result1.setText(description + " == " + "" + ": " + (description == ""));

prints "==:true"! (Android 4.4.2, API 19)

I run the same code in my desktop (javac 1.6.0_45) and it prints "==:false" as I expected.

Is it a bug in Android or is it intended behavior?

bongya
  • 150
  • 4
  • First, I thought this is a snippet from Android Open Source Project... anyway, I'm not sure, but based on [this answer](http://stackoverflow.com/a/513839/2821954), it may be related to string interning. (this is just my speculation though) – Andrew T. Apr 04 '14 at 01:29
  • `description.trim()` is empty so `replaceAll("\\s+|\\r+|\\n+", " ")` isn't doing anything. Bottom line is that there is no guarantee whether the comparison using `==` will return true or false. But the behavior of the `equals` method is well specified. So `description == ""` should be replaced with `description.equals("")` or `description.isEmpty()`. – Bhesh Gurung Apr 04 '14 at 01:30

1 Answers1

1

No, it's not a bug -- it's just an implementation detail leaking out.

The java compiler creates a pool of strings used in the code. The empty string is surely one of these. Any time you set a variable to the empty string at compile-time, it will point to the same instance of the empty string. So

String a = "";
String b = "";

if (a == b) {
   //this will always be true in practice, although I don't know if it's guaranteed
}

Now imagine that trim() and replaceAll() are implemented differently:

String trim() {
    byte[] b = getBytes();
    ...
    return new String(b, 0, len);
}

String replaceAll (String needle, String replacement) {
    String result = "";
    int pos = 0;
    while (indexOf(needle, pos) != -1) {
        ...
        result = result + replacement;
        pos = ...;
    }
    return result;
}

Because trim() calls a String constructor, it necessarily creates a new String. But replaceAll starts with an empty String and builds up. And the empty String that it starts with is the same empty string as all the other empty strings in your source code.

These are fake implementations -- it's just a hypothesis that this is how it works. It matches the observed data, but I didn't read the Android code. Still, it demonstrates that different implementations of similar functions could lead to the effect that you're seeing.

In other words, it's not a bug, but it's not a behavior you want to depend on. If you do want to depend on two strings that .equal() also being ==, you can use String.intern().