KumarAnkit
KumarAnkit

Reputation: 743

Why is this loop changed?

I just encountered this decompiled class file of my class:

MyClass

while ((line = reader.readLine()) != null) {
    System.out.println("line: " + line);
    if (i == 0) {
        colArr = line.split(Pattern.quote("|"));

    } else {
        i++;
    }
}

The while loop has been changed to a for loop in the class file:

Decompiled MyClass

for (String[] colArr = null; (line = reader.readLine()) != null; ++i) {
    System.out.println("line: " + line);
    if (i == 0) {
        colArr = line.split(Pattern.quote("|"));
    } else {
    }
}

Why has this loop been changed to a for? I think it might be another way of code optimization by the compiler, I could be wrong. I just wanted to know if it is, what advantages does a for loop provide over a while loop or other loop?
What is the category of such code optimizations?

Upvotes: 39

Views: 3123

Answers (4)

dgebert
dgebert

Reputation: 1484

In this situation changing while() to for() is not an optimization. There is simply no way to know from bytecode which one was used in a source code.

There are many situations when:

while(x)

is the same as:

for(;x;)

Suppose we have a three similar java applications - one with while() statement, and two with corresponting for(). First for() with stopping criterion only like in the standard while(), and second for() also with iterator declaration and incrementation.

APPLICATION #1 - SOURCE

public class While{
    public static void main(String args[]) {
        int i = 0;
        while(i<5){
            System.out.println(i);
            i++;
        }
    }
}

APPLICATION #2 - SOURCE

public class For{
    public static void main(String args[]) {
        int i = 0;
        for(; i<5 ;){
            System.out.println(i);
            i++;
        }
    }
}

APPLICATION #3 - SOURCE

public class For2{
    public static void main(String args[]) {
        for(int i=0;i<5;i++){
            System.out.println(i);
        }
    }
}

If we compile all of them we have got:

APPLICATION #1 - BYTECODE

public class While {
  public While();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: iconst_5
       4: if_icmpge     20
       7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: iload_1
      11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      14: iinc          1, 1
      17: goto          2
      20: return
}

APPLICATION #2 - BYTECODE

public class For {
  public For();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: iconst_5
       4: if_icmpge     20
       7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: iload_1
      11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      14: iinc          1, 1
      17: goto          2
      20: return
}

APPLICATION #3 - BYTECODE

public class For2 extends java.lang.Object{
public For2();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   istore_1
   2:   iload_1
   3:   iconst_5
   4:   if_icmpge       20
   7:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   10:  iload_1
   11:  invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   14:  iinc    1, 1
   17:  goto    2
   20:  return

}

So you can see, there is no difference associated with for and while usage.

Upvotes: 45

Marco13
Marco13

Reputation: 54639

As others have already pointed out: The decompiler (usually) cannot distinguish between different source codes that result in the same byte code.

Unfortunately, you did not provide the full code of the method. So the following contains some guesses about where and how this loop appears inside a method (and these guesses might, to some extent, distort the result).

But let's have a look at some roundtrips here. Consider the following class, containing methods with both versions of the code that you posted:

import java.io.BufferedReader;
import java.io.IOException;
import java.util.regex.Pattern;

public class DecompileExample {

    public static void methodA(BufferedReader reader) throws IOException {
        String line = null;
        int i = 0;
        while ((line = reader.readLine()) != null) {
            System.out.println("line: " + line);
            if (i == 0) {
                String[] colArr = line.split(Pattern.quote("|"));

            } else {
                i++;
            }
        }
    }

    public static void methodB(BufferedReader reader) throws IOException {
        String line = null;
        int i = 0;
        for (String[] colArr = null; (line = reader.readLine()) != null; ++i) {
            System.out.println("line: " + line);
            if (i == 0) {
                colArr = line.split(Pattern.quote("|"));
            } else {
            }
        }
    }
}

Compiling it with

javac DecompileExample.java -g:none

will create the corresponding class file. (Note: The -g:none parameter will cause the compiler to omit all debug information. The debug information might otherwise be used by the decompiler to reconstruct a more verbatim version of the original code, particularly, including the original variable names)

Now looking at the byte code of both methods, with

javap -c DecompileExample.class

will yield the following:

  public static void methodA(java.io.BufferedReader) throws java.io.IOException;
    Code:
       0: aconst_null
       1: astore_1
       2: iconst_0
       3: istore_2
       4: aload_0
       5: invokevirtual #2                  // Method java/io/BufferedReader.readLine:()Ljava/lang/String;
       8: dup
       9: astore_1
      10: ifnull        61
      13: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      16: new           #4                  // class java/lang/StringBuilder
      19: dup
      20: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
      23: ldc           #6                  // String line:
      25: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      28: aload_1
      29: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      32: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      35: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      38: iload_2
      39: ifne          55
      42: aload_1
      43: ldc           #10                 // String |
      45: invokestatic  #11                 // Method java/util/regex/Pattern.quote:(Ljava/lang/String;)Ljava/lang/String;
      48: invokevirtual #12                 // Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
      51: astore_3
      52: goto          4
      55: iinc          2, 1
      58: goto          4
      61: return

and

  public static void methodB(java.io.BufferedReader) throws java.io.IOException;
    Code:
       0: aconst_null
       1: astore_1
       2: iconst_0
       3: istore_2
       4: aconst_null
       5: astore_3
       6: aload_0
       7: invokevirtual #2                  // Method java/io/BufferedReader.readLine:()Ljava/lang/String;
      10: dup
      11: astore_1
      12: ifnull        60
      15: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      18: new           #4                  // class java/lang/StringBuilder
      21: dup
      22: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
      25: ldc           #6                  // String line:
      27: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      30: aload_1
      31: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      34: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      37: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      40: iload_2
      41: ifne          54
      44: aload_1
      45: ldc           #10                 // String |
      47: invokestatic  #11                 // Method java/util/regex/Pattern.quote:(Ljava/lang/String;)Ljava/lang/String;
      50: invokevirtual #12                 // Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
      53: astore_3
      54: iinc          2, 1
      57: goto          6
      60: return
}

(There is a small difference: The String[] colArr = null is translated into an

aconst null
astore_3

at the beginning of the second version. But this is one of the aspects that is related to parts of the code that you have omitted in the question).

You did not mention which one you are using, but the JD-GUI decompiler from http://jd.benow.ca/ decompiles this into the following:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.regex.Pattern;

public class DecompileExample
{
  public static void methodA(BufferedReader paramBufferedReader)
    throws IOException
  {
    String str = null;
    int i = 0;
    while ((str = paramBufferedReader.readLine()) != null)
    {
      System.out.println("line: " + str);
      if (i == 0) {
        String[] arrayOfString = str.split(Pattern.quote("|"));
      } else {
        i++;
      }
    }
  }

  public static void methodB(BufferedReader paramBufferedReader)
    throws IOException
  {
    String str = null;
    int i = 0;
    String[] arrayOfString = null;
    while ((str = paramBufferedReader.readLine()) != null)
    {
      System.out.println("line: " + str);
      if (i == 0) {
        arrayOfString = str.split(Pattern.quote("|"));
      }
      i++;
    }
  }
}

You can see that the code is the same for both cases (at least regarding the loop - there one more is a difference regarding the "dummy variables" that I had to introduce in order to compile it, but this is unrelated to the question, so to speak).

The tl;dr message is clear:

Different source codes can be compiled into the same byte code. Consequently, the same byte code can be decompiled into different source codes. But every decompiler has to settle for one version of the source code.

(A side note: I was a bit surprised to see that when compiling without -g:none (that is, when the debug information is retained), JD-GUI even somehow manages to reconstruct that the first one used a while-loop and the second one used a for-loop. But in general, and when the debug information is omitted, this is simply no longer possible).

Upvotes: 15

Wojtek Mlodzianowski
Wojtek Mlodzianowski

Reputation: 398

That's basically because of the nature of bytecode. Java bytecode is something like assembly language, so there are no such things as for and while loop, there is simply jump instruction: goto. So there may be no difference between while and for loop, Both can be compiled to similar code and decompiler is just making guess.

Upvotes: 6

prime
prime

Reputation: 15574

Both the for loop and the while loop code segments can be translated into similar machine code. After that when de-compiling the de-compiler has to pick one of the two possible scenarios.

I guess that is what's happening here.

simply:

compile(A) -> C

compile(B) -> C

So when you are given C, then there should be a guess to pick A or B

Upvotes: 5

Related Questions