Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 370
KumarAnkit Avatar asked Dec 14 '18 11:12

KumarAnkit


People also ask

Can a for loop condition change?

No it doesn't. The first statement in the for loop is executed only once. Once i is set, changing the value of the variable that was used to set i does not effect i . You are allowed to change the value of i explicitly within the for loop body though.

Why is my for loop running infinitely?

This is a silly example, but it's common for infinite loops to accidentally occur. Most of the times, it's because the variables used in the condition are not being updated correctly, or because the looping condition is in error.

Is the for loop outdated?

'For' loops are considered obsolete, hence we should avoid using them. In single threaded languages like JavaScript, the 'for' loop acts as a thread blocker, which hinders the scalability of a system. Debugging with 'for' loops can also be difficult.

What is for loop and how it works?

A "For" Loop is used to repeat a specific block of code a known number of times. For example, if we want to check the grade of every student in the class, we loop from 1 to that number. When the number of times is not known before hand, we use a "While" loop.


2 Answers

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.

like image 67
dgebert Avatar answered Oct 05 '22 22:10

dgebert


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).

like image 34
Marco13 Avatar answered Oct 05 '22 22:10

Marco13