JiSE icon indicating copy to clipboard operation
JiSE copied to clipboard

Unexpected use of final in emitted class

Open joinr opened this issue 3 years ago • 3 comments

Original java:

ThreadLocalRandom tlr = ThreadLocalRandom.current();
long l1 = tlr.nextLong(), l2 = tlr.nextLong();
char[] rt = new char[22];
 rt[21] = tbl[(int)l1 & 0x3f]; l1 = l1 >>> 6;
 rt[20] = tbl[(int)l1 & 0x3f]; l1 = l1 >>> 6;
 rt[19] = tbl[(int)l1 & 0x3f]; l1 = l1 >>> 6;

Corresponding JiSE:

(let [^ThreadLocalRandom tlr (ThreadLocalRandom/current)
      ^long  l1 (.nextLong tlr)
      ^long  l2 (.nextLong tlr)
      ^chars rt (new [char] 22)]
(aset rt 21 (tbl ^int (&  l1 0x3f)))(set! l1 (>>> l1 6))
(aset rt 20 (tbl ^int (&  l1 0x3f)))(set! l1 (>>> l1 6))
(aset rt 19 (tbl ^int (&  l1 0x3f)))(set! l1 (>>> l1 6))
...
)

Inspected with clj-decompiler, using (decompile ...), gives:

final char[] array = new char[22];
final ThreadLocalRandom current = ThreadLocalRandom.current();
final long nextLong = current.nextLong();
final long nextLong2 = current.nextLong();
array[21] = RandM.tbl[(int)(nextLong & 0x3FL)];
final long n = nextLong >>> 6;
array[20] = RandM.tbl[(int)(n & 0x3FL)];
final long n2 = n >>> 6;
array[19] = RandM.tbl[(int)(n2 & 0x3FL)];
...

long variable in let is set to final. Is there a way to allow it to mutate?

joinr avatar Sep 10 '20 09:09 joinr

Simple test from Peter Nagy:

^:public
(jise/defclass IsFinal
  ^:public ^:static ^long
  (defm test [^int y]
    (let [x 100]
      (set! x (+ x y))
      (.println System/out x)
      (set! x (+ x y))
      (+ x x))))
// Decompiling class: phoenix/flake/IsFinal
package phoenix.flake;

public class IsFinal
{
    public static long test(final int y) {
        final int x = 100 + y;
        System.out.println(x);
        final int n = x + y;
        return n + n;
    }
}

joinr avatar Sep 10 '20 09:09 joinr

clj-decompiler may be wrong

joinr avatar Sep 10 '20 09:09 joinr

Thank you for the feedback!

After some investigation, I'm figuring out what is going on. Finality information for local variables is not retained in the JVM bytecode and it only exists at compile time (as contrasted with fields’ finality, which is embedded as an access flag in the bytecode). So, nobody knows which local variable was declared final from the generated bytecode.

Probably, clj-java-decompiler (or its dependant library) infers a local variable to be declared final unless the decompiler witnesses an assignment for it. In fact, another Java decompiler JD doesn't seem to emit final at all for the same code:

package example.aobench;

class IsFinal {
  public static long test(int paramInt) {
    int i = 100;
    i += paramInt;
    System.out
      .println(i);
    i += paramInt;
    return (i + i);
  }
}

athos avatar Sep 10 '20 12:09 athos