GithubHelp home page GithubHelp logo

koresframework / kores-bytecodewriter Goto Github PK

View Code? Open in Web Editor NEW
1.0 2.0 0.0 1.58 MB

Translates Kores AST to JVM Bytecode

License: Other

Kotlin 64.69% Java 35.31%
jvm-bytecode bytecode-engineering java kotlin bytecode

kores-bytecodewriter's Introduction

Kores-BytecodeWriter

jitpack Discord Actions Packages

How to use Kores-BytecodeWriter

Kores-BytecodeWriter is now using GitHub Packages to distribute its binary files instead of jitpack.io (because jitpack still not support all JDK versions and sometimes jitpack.yml simply do not work).

In order to be able to download Kores-BytecodeWriter Artifacts, you will need to configure your global $HOME/.gradle/gradle.properties to store your username and a PAT with read:packages permission:

USERNAME=GITHUB_USERNAME
TOKEN=PAT

Then configure your build.gradle as the following:

def GITHUB_USERNAME = project.findProperty("USERNAME") ?: System.getenv("USERNAME")
def GITHUB_PAT = project.findProperty("TOKEN") ?: System.getenv("TOKEN")

repositories {
    mavenCentral()
    maven {
        url "https://maven.pkg.github.com/jonathanxd/jwiutils"
        credentials {
            username = GITHUB_USERNAME
            password = GITHUB_PAT
        }
    }
    maven {
        url "https://maven.pkg.github.com/jonathanxd/bytecodedisassembler"
        credentials {
            username = GITHUB_USERNAME
            password = GITHUB_PAT
        }
    }
    maven {
        url "https://maven.pkg.github.com/koresframework/kores"
        credentials {
            username = GITHUB_USERNAME
            password = GITHUB_PAT
        }
    }
    maven {
        url "https://maven.pkg.github.com/koresframework/kores-bytecodewriter"
        credentials {
            username = GITHUB_USERNAME
            password = GITHUB_PAT
        }
    }
}

dependencies {
    implementation("com.koresframework:kores:4.2.1.base") // Replace the version with the latest or a preferred one
    implementation("com.koresframework:kores-bytecodewriter:4.2.1.bytecode") // Replace the version with the latest or a preferred one
}

This is only needed because GitHub still not support unauthenticated artifact access.

kores-bytecodewriter's People

Contributors

jonathanxd avatar

Stargazers

 avatar

Watchers

 avatar  avatar

kores-bytecodewriter's Issues

Something wrong with Try-catch translation

I always thought that something wrong was happening with Try-Catch translation because the CodeAPITestBytecode.class was always inconsistent, now looking at the bytecode, there are various nop which isn't generated when the class is compiled with Javac.

Analyzing the bytecode, the problem looks to be with finally translation, the Inlined finally generated by CodeAPI does not generate extra try-catch nodes, but the Javac does.

Java:

        try {
            if (msg != null) {
                System.out.println(msg);
            }

            Object ref = System.out;
            throw new IllegalStateException("Error");
        } catch (IllegalStateException | IllegalArgumentException thr) {
            System.out.println("Rethrow from var 'thr'!");
            thr.printStackTrace();
        } catch (ClassNotFoundException | IOException tlr) {
            System.out.println("Rethrow from var 'tlr'!");
            tlr.printStackTrace();
        } finally {
            System.out.println("Finally!");
        }

CodeAPI:

     public static println(java.lang.Object arg0) { //(Ljava/lang/Object;)V
         <localVar:index=2 , name=ref , desc=Ljava/lang/Object;, sig=null, start=L1, end=L2>
         <localVar:index=2 , name=thr , desc=Ljava/lang/Throwable;, sig=null, start=L3, end=L4>
         <localVar:index=2 , name=tlr , desc=Ljava/lang/Throwable;, sig=null, start=L3, end=L5>
         <localVar:index=1 , name=test , desc=Lcom/github/jonathanxd/codeapi/test/asm/CodeAPITestBytecode;, sig=null, start=L6, end=L7>
         <localVar:index=0 , name=msg , desc=Ljava/lang/Object;, sig=null, start=L8, end=L7>

         TryCatch: L9 to L10 handled by L3: java/lang/IllegalArgumentException
         TryCatch: L9 to L10 handled by L3: java/lang/IllegalStateException
         TryCatch: L9 to L10 handled by L11: java/io/IOException
         TryCatch: L9 to L10 handled by L11: java/lang/ClassNotFoundException
         L8 {
             new com/github/jonathanxd/codeapi/test/asm/CodeAPITestBytecode
             dup
             invokespecial com/github/jonathanxd/codeapi/test/asm/CodeAPITestBytecode <init>(()V);
         }
         L6 {
             astore1
             getstatic java/lang/System.out:java.io.PrintStream
             aload1
             getfield com/github/jonathanxd/codeapi/test/asm/CodeAPITestBytecode.b:java.lang.String
             invokevirtual java/io/PrintStream println((Ljava/lang/Object;)V);
         }
         L9 {
             aload0
             ifnull L12
             getstatic java/lang/System.out:java.io.PrintStream
             aload0
             invokevirtual java/io/PrintStream println((Ljava/lang/Object;)V);
         }
         L12 {
             getstatic java/lang/System.out:java.io.PrintStream
         }
         L1 {
             astore2
             new java/lang/IllegalStateException
             dup
             ldc "Error" (java.lang.String)
             invokespecial java/lang/IllegalStateException <init>((Ljava/lang/String;)V);
             athrow
         }
         L10 {
             nop
             nop
             nop
             nop
             nop
             nop
             nop
             nop
             nop
             nop
             nop
             nop
             nop
             nop
             athrow
         }
         L2 {
             nop
             nop
             athrow
         }
         L3 {
             astore2
             getstatic java/lang/System.out:java.io.PrintStream
             ldc "Rethrow from var 'thr'!" (java.lang.String)
             invokevirtual java/io/PrintStream println((Ljava/lang/Object;)V);
             aload2
             invokevirtual java/lang/Throwable printStackTrace(()V);
             getstatic java/lang/System.out:java.io.PrintStream
             ldc "Finally!" (java.lang.String)
             invokevirtual java/io/PrintStream println((Ljava/lang/Object;)V);
         }
         L4 {
             goto L13
         }
         L11 {
             astore2
             getstatic java/lang/System.out:java.io.PrintStream
             ldc "Rethrow from var 'tlr'!" (java.lang.String)
             invokevirtual java/io/PrintStream println((Ljava/lang/Object;)V);
             aload2
             invokevirtual java/lang/Throwable printStackTrace(()V);
             getstatic java/lang/System.out:java.io.PrintStream
             ldc "Finally!" (java.lang.String)
             invokevirtual java/io/PrintStream println((Ljava/lang/Object;)V);
         }
         L5 {
             goto L13
         }
         L13 {
             return
         }
         L7 {
         }
     }
    !access: ACC_PUBLIC, ACC_STATIC (9)
    !parameter[name: msg, access:  (0)]
    public static void println(java.lang.Object) {
      desc: (Ljava/lang/Object;)V 
      maxStack: 3, maxLocals: 3 
      Label_0:
        new com.github.jonathanxd.codeapi.test.asm.CodeAPITestBytecode
        dup
        invokespecial com.github.jonathanxd.codeapi.test.asm.CodeAPITestBytecode.<init>()void (ownerIsInterface: false)
      Label_1:
        astore 1
        getstatic java.lang.System.out (type: java.io.PrintStream)
        aload 1
        getfield com.github.jonathanxd.codeapi.test.asm.CodeAPITestBytecode.b (type: java.lang.String)
        invokevirtual java.io.PrintStream.println(java.lang.Object)void (ownerIsInterface: false)
      Label_2:
        aload 0
        ifnull Label_3
        getstatic java.lang.System.out (type: java.io.PrintStream)
        aload 0
        invokevirtual java.io.PrintStream.println(java.lang.Object)void (ownerIsInterface: false)
      Label_3:
       FRAME[type: F_APPEND, locals: 1, local: {com.github.jonathanxd.codeapi.test.asm.CodeAPITestBytecode}, stacks: 0, stack: {}]
        getstatic java.lang.System.out (type: java.io.PrintStream)
      Label_4:
        astore 2
        new java.lang.IllegalStateException
        dup
        ldc "Error"              // type: java.lang.String
        invokespecial java.lang.IllegalStateException.<init>(java.lang.String)void (ownerIsInterface: false)
        athrow
      Label_5:
       FRAME[type: F_FULL, locals: 0, local: {}, stacks: 1, stack: {java.lang.Throwable}]
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        athrow
      Label_6:
       FRAME[type: F_SAME1, locals: 0, local: {}, stacks: 1, stack: {java.lang.Throwable}]
        nop
        nop
        athrow
      Label_7:
       FRAME[type: F_FULL, locals: 2, local: {java.lang.Object, com.github.jonathanxd.codeapi.test.asm.CodeAPITestBytecode}, stacks: 1, stack: {java.lang.RuntimeException}]
        astore 2
        getstatic java.lang.System.out (type: java.io.PrintStream)
        ldc "Rethrow from var 'thr'!"              // type: java.lang.String
        invokevirtual java.io.PrintStream.println(java.lang.Object)void (ownerIsInterface: false)
        aload 2
        invokevirtual java.lang.Throwable.printStackTrace()void (ownerIsInterface: false)
        getstatic java.lang.System.out (type: java.io.PrintStream)
        ldc "Finally!"              // type: java.lang.String
        invokevirtual java.io.PrintStream.println(java.lang.Object)void (ownerIsInterface: false)
      Label_8:
        goto Label_11
      Label_9:
       FRAME[type: F_SAME1, locals: 0, local: {}, stacks: 1, stack: {java.lang.Exception}]
        astore 2
        getstatic java.lang.System.out (type: java.io.PrintStream)
        ldc "Rethrow from var 'tlr'!"              // type: java.lang.String
        invokevirtual java.io.PrintStream.println(java.lang.Object)void (ownerIsInterface: false)
        aload 2
        invokevirtual java.lang.Throwable.printStackTrace()void (ownerIsInterface: false)
        getstatic java.lang.System.out (type: java.io.PrintStream)
        ldc "Finally!"              // type: java.lang.String
        invokevirtual java.io.PrintStream.println(java.lang.Object)void (ownerIsInterface: false)
      Label_10:
        goto Label_11
      Label_11:
       FRAME[type: F_APPEND, locals: 1, local: {java.lang.Exception}, stacks: 0, stack: {}]
        return
      Label_12:
      TryCatchBlocks {
        start: Label_2, end: Label_5, handler: Label_7, exception: java.lang.IllegalArgumentException
        start: Label_2, end: Label_5, handler: Label_7, exception: java.lang.IllegalStateException
        start: Label_2, end: Label_5, handler: Label_9, exception: java.io.IOException
        start: Label_2, end: Label_5, handler: Label_9, exception: java.lang.ClassNotFoundException
      }
      LocalVariables {
        index: 2, name: ref, start: Label_4, end: Label_6, type: java.lang.Object, signature: null
        index: 2, name: thr, start: Label_7, end: Label_8, type: java.lang.Throwable, signature: null
        index: 2, name: tlr, start: Label_7, end: Label_10, type: java.lang.Throwable, signature: null
        index: 1, name: test, start: Label_1, end: Label_12, type: com.github.jonathanxd.codeapi.test.asm.CodeAPITestBytecode, signature: null
        index: 0, name: msg, start: Label_0, end: Label_12, type: java.lang.Object, signature: null
      }
}
  • Java:
     public static println(java.lang.Object arg0) { //(Ljava/lang/Object;)V
         <localVar:index=1 , name=ref , desc=Ljava/io/PrintStream;, sig=null, start=L1, end=L2>
         <localVar:index=2 , name=f , desc=Ljava/io/File;, sig=null, start=L3, end=L2>
         <localVar:index=1 , name=thr , desc=Ljava/lang/RuntimeException;, sig=null, start=L4, end=L5>
         <localVar:index=1 , name=tlr , desc=Ljava/lang/Exception;, sig=null, start=L6, end=L7>
         <localVar:index=0 , name=msg , desc=Ljava/lang/Object;, sig=null, start=L8, end=L9>

         TryCatch: L8 to L2 handled by L2: java/lang/IllegalArgumentException
         TryCatch: L8 to L2 handled by L2: java/lang/IllegalStateException
         TryCatch: L8 to L2 handled by L10: java/io/IOException
         TryCatch: L8 to L2 handled by L10: java/lang/ClassNotFoundException
         TryCatch: L8 to L5 handled by L11: Type is null.
         TryCatch: L10 to L7 handled by L11: Type is null.
         L8 {
             aload0
             ifnull L12
         }
         L13 {
             getstatic java/lang/System.out:java.io.PrintStream
             aload0
             invokevirtual java/io/PrintStream println((Ljava/lang/Object;)V);
         }
         L12 {
             getstatic java/lang/System.out:java.io.PrintStream
             astore1
         }
         L1 {
             new java/io/File
             dup
             ldc "s" (java.lang.String)
             invokespecial java/io/File <init>((Ljava/lang/String;)V);
             astore2
         }
         L3 {
             aload2
             invokevirtual java/io/File getCanonicalPath(()Ljava/lang/String;);
             pop
         }
         L14 {
             ldc "java.io.File" (java.lang.String)
             invokestatic java/lang/Class forName((Ljava/lang/String;)Ljava/lang/Class;);
             pop
         }
         L15 {
             new java/lang/IllegalStateException
             dup
             ldc "Error" (java.lang.String)
             invokespecial java/lang/IllegalStateException <init>((Ljava/lang/String;)V);
             athrow
         }
         L2 {
             astore1
         }
         L4 {
             getstatic java/lang/System.out:java.io.PrintStream
             ldc "Rethrow from var 'thr'!" (java.lang.String)
             invokevirtual java/io/PrintStream println((Ljava/lang/Object;)V);
         }
         L16 {
             aload1
             invokevirtual java/lang/RuntimeException printStackTrace(()V);
         }
         L5 {
             getstatic java/lang/System.out:java.io.PrintStream
             ldc "Finally!" (java.lang.String)
             invokevirtual java/io/PrintStream println((Ljava/lang/Object;)V);
         }
         L17 {
             goto L18
         }
         L10 {
             astore1
         }
         L6 {
             getstatic java/lang/System.out:java.io.PrintStream
             ldc "Rethrow from var 'tlr'!" (java.lang.String)
             invokevirtual java/io/PrintStream println((Ljava/lang/Object;)V);
         }
         L19 {
             aload1
             invokevirtual java/lang/Exception printStackTrace(()V);
         }
         L7 {
             getstatic java/lang/System.out:java.io.PrintStream
             ldc "Finally!" (java.lang.String)
             invokevirtual java/io/PrintStream println((Ljava/lang/Object;)V);
         }
         L20 {
             goto L18
         }
         L11 {
             astore3
             getstatic java/lang/System.out:java.io.PrintStream
             ldc "Finally!" (java.lang.String)
             invokevirtual java/io/PrintStream println((Ljava/lang/Object;)V);
             aload3
             athrow
         }
         L18 {
             return
         }
         L9 {
         }
     }
  • BytecodeDisassembler version:
    !access: ACC_PUBLIC, ACC_STATIC (9)
    public static void println(java.lang.Object) {
      desc: (Ljava/lang/Object;)V 
      maxStack: 3, maxLocals: 4 
      Label_0:
       LINE 7 -> Label_0
        aload 0
        ifnull Label_2
      Label_1:
       LINE 8 -> Label_1
        getstatic java.lang.System.out (type: java.io.PrintStream)
        aload 0
        invokevirtual java.io.PrintStream.println(java.lang.Object)void (ownerIsInterface: false)
      Label_2:
       LINE 10 -> Label_2
       FRAME[type: F_SAME, locals: 0, local: {}, stacks: 0, stack: {}]
        getstatic java.lang.System.out (type: java.io.PrintStream)
        astore 1
      Label_3:
       LINE 11 -> Label_3
        new java.io.File
        dup
        ldc "s"              // type: java.lang.String
        invokespecial java.io.File.<init>(java.lang.String)void (ownerIsInterface: false)
        astore 2
      Label_4:
       LINE 12 -> Label_4
        aload 2
        invokevirtual java.io.File.getCanonicalPath()java.lang.String (ownerIsInterface: false)
        pop
      Label_5:
       LINE 13 -> Label_5
        ldc "java.io.File"              // type: java.lang.String
        invokestatic java.lang.Class.forName(java.lang.String)java.lang.Class (ownerIsInterface: false)
        pop
      Label_6:
       LINE 14 -> Label_6
        new java.lang.IllegalStateException
        dup
        ldc "Error"              // type: java.lang.String
        invokespecial java.lang.IllegalStateException.<init>(java.lang.String)void (ownerIsInterface: false)
        athrow
      Label_7:
       LINE 15 -> Label_7
       FRAME[type: F_SAME1, locals: 0, local: {}, stacks: 1, stack: {Reference[java.lang.RuntimeException]}]
        astore 1
      Label_8:
       LINE 16 -> Label_8
        getstatic java.lang.System.out (type: java.io.PrintStream)
        ldc "Rethrow from var 'thr'!"              // type: java.lang.String
        invokevirtual java.io.PrintStream.println(java.lang.Object)void (ownerIsInterface: false)
      Label_9:
       LINE 17 -> Label_9
        aload 1
        invokevirtual java.lang.RuntimeException.printStackTrace()void (ownerIsInterface: false)
      Label_10:
       LINE 22 -> Label_10
        getstatic java.lang.System.out (type: java.io.PrintStream)
        ldc "Finally!"              // type: java.lang.String
        invokevirtual java.io.PrintStream.println(java.lang.Object)void (ownerIsInterface: false)
      Label_11:
       LINE 23 -> Label_11
        goto Label_18
      Label_12:
       LINE 18 -> Label_12
       FRAME[type: F_SAME1, locals: 0, local: {}, stacks: 1, stack: {Reference[java.lang.Exception]}]
        astore 1
      Label_13:
       LINE 19 -> Label_13
        getstatic java.lang.System.out (type: java.io.PrintStream)
        ldc "Rethrow from var 'tlr'!"              // type: java.lang.String
        invokevirtual java.io.PrintStream.println(java.lang.Object)void (ownerIsInterface: false)
      Label_14:
       LINE 20 -> Label_14
        aload 1
        invokevirtual java.lang.Exception.printStackTrace()void (ownerIsInterface: false)
      Label_15:
       LINE 22 -> Label_15
        getstatic java.lang.System.out (type: java.io.PrintStream)
        ldc "Finally!"              // type: java.lang.String
        invokevirtual java.io.PrintStream.println(java.lang.Object)void (ownerIsInterface: false)
      Label_16:
       LINE 23 -> Label_16
        goto Label_18
      Label_17:
       LINE 22 -> Label_17
       FRAME[type: F_SAME1, locals: 0, local: {}, stacks: 1, stack: {Reference[java.lang.Throwable]}]
        astore 3
        getstatic java.lang.System.out (type: java.io.PrintStream)
        ldc "Finally!"              // type: java.lang.String
        invokevirtual java.io.PrintStream.println(java.lang.Object)void (ownerIsInterface: false)
        aload 3
        athrow
      Label_18:
       LINE 24 -> Label_18
       FRAME[type: F_SAME, locals: 0, local: {}, stacks: 0, stack: {}]
        return
      Label_19:
      TryCatchBlocks {
        start: Label_0, end: Label_7, handler: Label_7, exception: java.lang.IllegalArgumentException
        start: Label_0, end: Label_7, handler: Label_7, exception: java.lang.IllegalStateException
        start: Label_0, end: Label_7, handler: Label_12, exception: java.io.IOException
        start: Label_0, end: Label_7, handler: Label_12, exception: java.lang.ClassNotFoundException
        start: Label_0, end: Label_10, handler: Label_17, exception: null
        start: Label_12, end: Label_15, handler: Label_17, exception: null
      }
      LocalVariables {
        index: 1, name: ref, start: Label_3, end: Label_7, type: java.io.PrintStream, signature: null
        index: 2, name: f, start: Label_4, end: Label_7, type: java.io.File, signature: null
        index: 1, name: thr, start: Label_8, end: Label_10, type: java.lang.RuntimeException, signature: null
        index: 1, name: tlr, start: Label_13, end: Label_15, type: java.lang.Exception, signature: null
        index: 0, name: msg, start: Label_0, end: Label_19, type: java.lang.Object, signature: null
      }
    }

The CodeAPI version looks weird.

Indify String Concatenation

In Javac 9, String concatenation is compiled to a dynamic invocation of a bootstrap that concatenates the string instead of using StringBuilder.

The feature should be enabled only if TARGET_PLATFORM: Option<Target> is Target.JAVA_9 and disabled if LEGACY_CONCAT: Option<Boolean> is true.

http://openjdk.java.net/jeps/280

[Generator 3 & 4] Incorret generation of `OR` statements.

Statements:

if(a && b && c || d) {
  System.out.println("true");
} else {
  System.out.println("false");
}

Javac bytecode:

         0: iload_1
         1: ifeq          12
         4: iload_2
         5: ifeq          12
         8: iload_3
         9: ifne          17
        12: iload         4
        14: ifeq          28
        17: getstatic     #38                 // Field java/lang/System.out:Ljava/io/PrintStream;
        20: ldc           #22                 // String true
        22: invokevirtual #39                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        25: goto          36
        28: getstatic     #38                 // Field java/lang/System.out:Ljava/io/PrintStream;
        31: ldc           #26                 // String false
        33: invokevirtual #39                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        36: return

Code API bytecode:

         4: iload_1
         5: ifeq          32
         8: iload_2
         9: ifeq          32
        12: iload_3
        13: ifne          21
        16: iload         4
        18: ifeq          32
        21: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
        24: ldc           #22                 // String true
        26: invokevirtual #28                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        29: goto          40
        32: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
        35: ldc           #30                 // String false
        37: invokevirtual #28                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        40: return

Calling codeapi(true, false, true, true) prints false and calling java(true, false, true, true) prints true.

Javac compiles to if((a && b && c) || d) and CodeAPI-BytecodeWeiter to if(a && b && (c || d))

Control Flow Graph (I'm bad at graphs):

Java
Java

CodeAPI
CodeAPI

More auto-casting

Add more auto-casting in locations where type is explicitly provided (this means that you know that type casting will occur), like what I did in e80d4a3 (Lines 90-93)

Support local classes.

Local classes can be expressed in CodeAPI but BytecodeWriter doesn't support the bytecode generation of local classes.

CodeAPI generated bytecode debugging

To debug classes generated by BytecodeGenerator you should to enable the VISIT_LINES option:

BytecodeGenerator generator = ...
bytecodeGenerator.getOptions().set(BytecodeOptions.VISIT_LINES, VisitLineType.FOLLOW_CODE_SOURCE);

There's three types of line visit:

  • DISABLED
    • Disable the line visit (default)
  • INCREMENTAL
    • Incremental, for each expression visit a line
  • FOLLOW_CODE_SOURCE
    • Follow the code source order, for each CodeSource index visit a line (when a CodeSource is found inside another CodeSource, the line visitor will apply a offset).

After defining the VISIT_LINES option, the BytecodeVisitor will visit lines, and these lines can be found in the disassembled bytecode class (BytecodeClass.disassembledCode).

Create mappings for Switch on Enums

Currently Enums are translated to a simple ordinal switch, to improve we need to map enum ordinals to another value (from 1 to values.length). This improvement will avoid issues when enums classes being changed.

public void a(MyEnum e) {
  switch(SwitchOnEnum$Mappings.map[e.ordinal()]) {
    case 1: ....
    case 2: ...
    default: ...
  }
}

synthetic class SwitchOnEnum$Mappings { 
  static final int[] map = new int[MyEnum.length];
  static {
    try {
      map[MyEnum.A.ordinal()] = 1;
    } catch(NoSouchFieldError e) {}
    try {
      map[MyEnum.B.ordinal()] = 2;
    } catch(NoSouchFieldError e) {}
  }
}

If another enum on switch for the same enum type is found, CodeAPI should reuse the generated mappings to map other values.

Improve variable store

Currently CodeAPI only increments the variable count instead of reuse variable slots of other frames.

CodeAPI-BytecodeWriter:

public int a(boolean b) {
  if(b) {
    int x = 10; // astore 2
    return x;
  } else {
    int x2 = 11; // astore 3
    return x2;
  }
}

Javac:

public int a(boolean b) {
  if(b) {
    int x = 10; // astore 2
    return x;
  } else {
    int x2 = 11;  // astore 2
    return x2;
  }
}

This issue relates to #4, but issue #4 was dropped, then this issue will cover a particular problem that #4 would solve.

Missing pop after unused values.

Since the existence of CodeAPI, generated bytecode does not have pop after unused expression values. This leads to an uncommon bug with control flow. Example:

if (a) run() else run2()

Assuming that run returns void and run2 returns Object (but occurs with any value type), the generated bytecode will be incorrect, because when the flow reach the end, a value of type Object may or may not be present, so the Frame types cannot be calculated correctly and even if it could be, JVM will fail to load the class.

CodeAPI-BytecodeWriter should track carefully when expression is used as a value or not, so the bytecode will be only incorrect if the user was made a mistake. The current workaround is to use BytecodeWriter Pop CodeInstruction after run2 invocation:

if (a) run() else run2(); pop

Or to call another method on if body which returns Object, so frame types are calculated correctly:

if (a) runWithReturn() else run2(); pop

Assuming that both runWithReturn and run2 returns either an Object instance or values of same primitive type.

Use isInterface of Type instead of InvokeType

Invocation to interface default methods fail in Java 9 because BytecodeWriter uses InvokeType.isInterface to determine if the method is a interface method, this works fine in Java 8 because the verifier does not care about it, but in Java 9 it do. Default interface method is invoke using InvokeType.INVOKE_SPECIAL, which returns false for isInterface, so we should use CodeType.isInterface to generate invocations.

Copy reference to array in foreach

Currently foreach directly accesses the array, but the approach of Javac is to copy this array to avoid the array reference that is in foreach to be changed like this:

String[] ss = new String[]{"A", "B", "C"};
for (String s : ss) {
    if (Objects.equals(s, "B")) {
        ss = new String[]{"D"};
    }
}

Synthetic accessors generated when self type is used in private accesses.

When you use self type to access private members, BytecodeGenerator generates invocation to synthetic accessor and generates the accessors. Example:

TypeDeclaration decl = ClassDeclaration.Builder.builder()
        .modifiers(CodeModifier.PUBLIC)
        .specifiedName("com.MyClass")
        .fields(new ArrayList<>())
        .constructors(new ArrayList<>())
        .methods(new ArrayList<>())
        .build();

decl.getFields().add(PartFactory.fieldDec()
        .modifiers(CodeModifier.PRIVATE, CodeModifier.STATIC, CodeModifier.FINAL)
        .type(String.class)
        .name("f1")
        .value(Literals.STRING("Test"))
        .build());

decl.getFields().add(PartFactory.fieldDec()
        .modifiers(CodeModifier.PRIVATE, CodeModifier.FINAL)
        .type(Integer.TYPE)
        .name("f2")
        .value(Literals.INT(10))
        .build());

decl.getMethods().add(PartFactory.methodDec()
        .modifiers(CodeModifier.PUBLIC, CodeModifier.STATIC)
        .returnType(String.class)
        .name("getStaticF1")
        .body(CodeSource.fromPart(
                Factories.returnValue(String.class,
                        Factories.accessStaticField(decl, String.class, "f1"))
        ))
        .build());

decl.getMethods().add(PartFactory.methodDec()
        .publicModifier()
        .returnType(Integer.TYPE)
        .name("getF2")
        .body(CodeSource.fromPart(
                Factories.returnValue(Integer.TYPE,
                        Factories.accessField(decl, Access.THIS, Integer.TYPE, "f2"))
        ))
        .build());

Synthetic accessor invocation is generated to both accesses in methods (synthetic accessors are also generated).

Generated class:

md5: 4cf71643473a9310ca98888bd675264f

version: Java 8 (52)
access: ACC_PUBLIC (33)

source: MyClass.cai

public class com.MyClass extends java.lang.Object {

    !access: ACC_PRIVATE, ACC_STATIC, ACC_FINAL (26)
    private static final java.lang.String f1 = Test
    !access: ACC_PRIVATE, ACC_FINAL (18)
    private final int f2

    !access: ACC_PUBLIC, ACC_STATIC (9)
    public static java.lang.String getStaticF1() {
      desc: ()Ljava/lang/String; 
      maxStack: 0, maxLocals: 0 
      Label_0:
       LINE 1 -> Label_0
        invokestatic com.MyClass.accessor$(com.MyClass)java.lang.String (ownerIsInterface: false)
        areturn
    }
    
    !access: ACC_PUBLIC (1)
    public int getF2() {
      desc: ()I 
      maxStack: 1, maxLocals: 1 
      Label_0:
       LINE 2 -> Label_0
        aload 0
        invokestatic com.MyClass.accessor$0(com.MyClass)int (ownerIsInterface: false)
        ireturn
      Label_1:
      LocalVariables {
        index: 0, name: this, start: Label_0, end: Label_1, type: com.MyClass, signature: null
      }
    }
    
    !access: ACC_PUBLIC (1)
    public void <init>() {
      desc: ()V 
      maxStack: 2, maxLocals: 1 
      Label_0:
        aload 0
        invokespecial java.lang.Object.<init>()void (ownerIsInterface: false)
        aload 0
        bipush 10
        putfield com.MyClass.f2 (type: int)
        return
      Label_1:
      LocalVariables {
        index: 0, name: this, start: Label_0, end: Label_1, type: com.MyClass, signature: null
      }
    }
    
    !access: PACKAGE_PRIVATE, ACC_STATIC (8)
    static void <clinit>() {
      desc: ()V 
      maxStack: 0, maxLocals: 0 
        return
    }
    
    !access: PACKAGE_PRIVATE, ACC_STATIC, ACC_SYNTHETIC (4104)
    !parameter[name: this, access:  (0)]
    static java.lang.String accessor$(com.MyClass) {
      desc: (Lcom/MyClass;)Ljava/lang/String; 
      maxStack: 1, maxLocals: 1 
      Label_0:
       LINE 3 -> Label_0
        aload 0
        getfield com.MyClass.f1 (type: java.lang.String)
        areturn
      Label_1:
      LocalVariables {
        index: 0, name: this, start: Label_0, end: Label_1, type: com.MyClass, signature: null
      }
    }
    
    !access: PACKAGE_PRIVATE, ACC_STATIC, ACC_SYNTHETIC (4104)
    !parameter[name: this, access:  (0)]
    static int accessor$0(com.MyClass) {
      desc: (Lcom/MyClass;)I 
      maxStack: 1, maxLocals: 1 
      Label_0:
       LINE 4 -> Label_0
        aload 0
        getfield com.MyClass.f2 (type: int)
        ireturn
      Label_1:
      LocalVariables {
        index: 0, name: this, start: Label_0, end: Label_1, type: com.MyClass, signature: null
      }
    }
    
}

Line find

CodeAPI-BytecodeWriter provides a way to generate line numbers based on CodeSource index. To help to find out the CodePart that causes the error, we should provide a findLine utility method for CodeSource.

Usage example:

val codeSource = CodeAPI.sourceOfParts(
  a, // 0
  b, // 1
  CodeAPI.sourceOfParts(
    c, // 2
    d // 3
  )
)


CodeSourceUtil.findLine(3, codeSource) // d

Minor issues

  • Default constructors are generated at the end of class instead after fields.
  • Static block is generated even if the body is empty
  • No option to disable variable name generation

20/08/2017:

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.