usethesource / flybytes

Flybytes is an intermediate language between JVM bytecode and software languages (DSLs, PLs), for compilation and decompilation.
BSD 2-Clause "Simplified" License
16 stars 6 forks source link

LinkageError / java.lang.ClassFormatError: Illegal class name "Ljava/lang/String;" #23

Closed bys1 closed 1 year ago

bys1 commented 1 year ago

I am compiling a Test.class file by calling the following function:

void compileF() {
    str vl = "io.usethesource.vallang.";
    Class cl = class(
        object("Test"),
        modifiers = {\public(), \final()},
        methods = [
            constructor(
                \public(),
                [],
                [
                    invokeSuper([], []),
                    \decl(array(string()), "args", init = newArray(
                        array(string()),
                        iconst(3)
                    )),
                    \return()
                ]
            )
        ]
    );
    compileClass(cl, |cwd:///Test.class|);
}

When running this using java Test I get the following error in the terminal:

Error: LinkageError occurred while loading main class Test
    java.lang.ClassFormatError: Illegal class name "Ljava/lang/String;" in class file Test

In case it helps you: I got a stack trace in the Rascal shell when I tried loading a bigger class file using a classloader:

|file:///Users/bys1/Documents/RUG/FBEG/flybytes-evalgen/src/main/rascal/Proto.rsc|(1159,62,<42,0>,<44,35>): Java("ClassFormatError","Illegal class name \"Ljava/lang/String;\" in class file FuncEnv")
    at java.lang.ClassLoader.defineClass1(|unknown:///ClassLoader.java|(0,0,<0,0>,<0,0>))
    at java.lang.ClassLoader.defineClass(|unknown:///ClassLoader.java|(0,0,<1012,0>,<1012,0>))
    at java.security.SecureClassLoader.defineClass(|unknown:///SecureClassLoader.java|(0,0,<150,0>,<150,0>))
    at jdk.internal.loader.BuiltinClassLoader.defineClass(|unknown:///BuiltinClassLoader.java|(0,0,<862,0>,<862,0>))
    at jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(|unknown:///BuiltinClassLoader.java|(0,0,<760,0>,<760,0>))
    at jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(|unknown:///BuiltinClassLoader.java|(0,0,<681,0>,<681,0>))
    at jdk.internal.loader.BuiltinClassLoader.loadClass(|unknown:///BuiltinClassLoader.java|(0,0,<639,0>,<639,0>))
    at jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(|unknown:///ClassLoaders.java|(0,0,<188,0>,<188,0>))
    at java.lang.ClassLoader.loadClass(|unknown:///ClassLoader.java|(0,0,<520,0>,<520,0>))
    at java.lang.Class.getDeclaredConstructors0(|unknown:///Class.java|(0,0,<0,0>,<0,0>))
    at java.lang.Class.privateGetDeclaredConstructors(|unknown:///Class.java|(0,0,<3373,0>,<3373,0>))
    at java.lang.Class.getConstructor0(|unknown:///Class.java|(0,0,<3578,0>,<3578,0>))
    at java.lang.Class.getConstructor(|unknown:///Class.java|(0,0,<2271,0>,<2271,0>))
    at Proto.genEval(|unknown:///Proto.java|(0,0,<49,0>,<49,0>))
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(|unknown:///NativeMethodAccessorImpl.java|(0,0,<0,0>,<0,0>))
    at genEval(|prompt:///|(13,4,<1,13>,<1,17>)ok
rascal>:quit
)

The error does not appear when removing the decl part from the code above.

bys1 commented 1 year ago

When I decompile Test.class, the constructor is as follows:

decl(
          array(string()),
          "args"),
        invokeSuper(
          constructorDesc([]),
          []),
        store(
          "args",
          newArray(
            object("Ljava.lang.String"),
            const(
              integer(),
              3))),
        return()

When I compile the following Java class using javac:

public final class Test2 {

    public Test2() {
        String[] args = new String[3];
    }

}

and decompile it with Flybytes, the constructor is:

asm([
            ALOAD(0,LINE=6),
            INVOKESPECIAL(
              object("java.lang.Object"),
              constructorDesc([]),
              false,
              LINE=6)
          ]),
        do(newArray(
            string(),
            const(
              integer(),
              3))),
        asm([ASTORE(1,LINE=6)]),
        return()

So in the Flybytes-compiled Test file, it calls newArray with the type object("Ljava.lang.String") and in Test2, string() is used. Note that when compiling using Flybytes, I cannot use newArray with string(), but have to use array(string()) instead, because otherwise I get the following error:

|jar+file:///Users/bys1/.m2/repository/org/rascalmpl/flybytes/0.1.10/flybytes-0.1.10.jar!/src/lang/flybytes/Compiler.rsc|(1637,272,<25,0>,<27,123>): Java("IllegalArgumentException","arg should be an array type")
    at lang.flybytes.internal.ClassCompiler$Compile.newArrayExp(|unknown:///ClassCompiler.java|(0,0,<2687,0>,<2687,0>))
    at lang.flybytes.internal.ClassCompiler$Compile.expr(|unknown:///ClassCompiler.java|(0,0,<2247,0>,<2247,0>))
    at lang.flybytes.internal.ClassCompiler$Compile.storeStat(|unknown:///ClassCompiler.java|(0,0,<2134,0>,<2134,0>))
    at lang.flybytes.internal.ClassCompiler$Compile.declareVariable(|unknown:///ClassCompiler.java|(0,0,<703,0>,<703,0>))
    at lang.flybytes.internal.ClassCompiler$Compile.declStat(|unknown:///ClassCompiler.java|(0,0,<2038,0>,<2038,0>))
    at lang.flybytes.internal.ClassCompiler$Compile.statement(|unknown:///ClassCompiler.java|(0,0,<831,0>,<831,0>))
    at lang.flybytes.internal.ClassCompiler$Compile.statements(|unknown:///ClassCompiler.java|(0,0,<812,0>,<812,0>))
    at lang.flybytes.internal.ClassCompiler$Compile.method(|unknown:///ClassCompiler.java|(0,0,<646,0>,<646,0>))
    at lang.flybytes.internal.ClassCompiler$Compile.methods(|unknown:///ClassCompiler.java|(0,0,<535,0>,<535,0>))
    at lang.flybytes.internal.ClassCompiler$Compile.compileClass(|unknown:///ClassCompiler.java|(0,0,<345,0>,<345,0>))
    at lang.flybytes.internal.ClassCompiler.compileClass(|unknown:///ClassCompiler.java|(0,0,<104,0>,<104,0>))
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(|unknown:///NativeMethodAccessorImpl.java|(0,0,<0,0>,<0,0>))
    at compileClass(|jar+file:///Users/bys1/.m2/repository/org/rascalmpl/flybytes/0.1.10/flybytes-0.1.10.jar!/src/lang/flybytes/Compiler.rsc|(1902,5,<27,116>,<27,121>))
    at $shell$(|prompt:///|(0,11,<1,0>,<1,11>)ok
rascal>
bys1 commented 1 year ago

I just found out that I do NOT get the LinkageError/ClassFormatError when explicitly giving the String class rather than using the string() type:

\decl(array(string()), "args", init = newArray(
                        array(object("java.lang.String")),
                        iconst(3)
                    )),

Using newArray with array(object("java.lang.String")) rather than array(string()) works fine.

jurgenvinju commented 1 year ago

That's very nice analysis. I can probably fix this bug based on your input. Maybe tomorrow. Today the time is up.

jurgenvinju commented 1 year ago

Note to self: typically string() should act as a simple alias for object("java.lang.String"). Possibly in the code for generating array declarations I missed a case distinction.