konsoletyper / teavm

Compiles Java bytecode to JavaScript, WebAssembly and C
https://teavm.org
Apache License 2.0
2.62k stars 261 forks source link

JSO regression with returned T[] arrays when T is a custom JSObject type #869

Closed McMurat closed 10 months ago

McMurat commented 10 months ago

There is a regression in 10.0.0-SNAPSHOT: There is an issue when an JSObject type array is returned in a JSO interface called from JavaScript. TeaVM calls otji_JS_wrap on such arrays before returning, which will otji_JSWrapper_unwrap each element, but as the $js field is not present for JSObject types, the return value is an array of many undefined values: [undefined, undefined, ...]

This happened to me after upgrading from version 8.1.0.

konsoletyper commented 10 months ago

Can you provide the code that reproduces the issue?

McMurat commented 10 months ago
public interface JsTool extends JSObject { }
public class JsToolImpl implements JsTool { }

Then in some class with an exported getTools() method:

public JsTool[] getTools() {
    JsTool[] t = new JsTool[1];
    t[0] = new JsToolImpl();
    return t;
}

Calling this in JS will return [ undefined ].

konsoletyper commented 10 months ago

@McMurat I wrote following test:

    @Test
    public void createArrayAndReturnToJS() {
        assertEquals("23,42", concatFoo(() -> new J[] {
                () -> 23,
                () -> 42
        }));
    }

    @JSBody(params = "supplier", script = "let array = supplier.get(); "
            + "return array[0].foo() + ',' + array[1].foo();")
    private static native String concatFoo(JArraySupplier supplier);

    interface JArraySupplier extends JSObject {
        J[] get();
    }

    interface J extends JSObject {
        int foo();
    }

and it passes. What am I doing wrong?

McMurat commented 10 months ago

@konsoletyper Thank you very much for your feedback.

Okay, that's interesting that it works when using the JSBody annotation here. I created a minimal example project to reproduce the issue for you:

package org.example;

import org.teavm.jso.JSBody;
import org.teavm.jso.JSObject;

public class Main {
    @JSBody(params = {"provider"}, script = "initializeContent(provider);")
    static native void initializeContent(JsProvider provider);

    public static void main(String[] args) {
        initializeContent(new JsProviderImpl());
    }
}

class JsProviderImpl implements JsProvider {
    @Override
    public JsTool[] getTools() {
        return new JsTool[] { new JsToolImpl() };
    }
}

interface JsProvider extends JSObject {
    JsTool[] getTools();
}

interface JsTool extends JSObject {}
class JsToolImpl implements JsTool {}

and here is the HTML/JS:

<html>
<head>
    <title></title>
    <script src="build/generated/teavm/js/TestTeaVM.js"></script>
    <script>
        window.initializeContent = (provider) => {
            const tools = provider.getTools();
            console.log(tools);
        };
        main();
    </script>
</head>
<body></body>
</html>

and the gradle build.gradle (groovy):

plugins {
    id 'java'
    id "war"
    id "org.teavm" version "0.9.0"
}

group = 'org.example'
version = '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

teavm.js {
    addedToWebApp = false
    obfuscated = true
    mainClass = "org.example.Main"
    entryPointName = "main"
    outOfProcess = true
}

dependencies {
    implementation teavm.libs.jso
}

When running this example in the browser, you will get [undefined] in the console. Using the developer tools will also confirm that this is indeed the value undefined and not just a string "undefined". This issue ís already present in 0.9.0, so i am stuck with 0.8.1 at the moment.

konsoletyper commented 10 months ago

The difference between your test and mine was in using lambdas. This should not be the case, but somewhere after 0.9.0 I broken mechanism that processes invokedynamics before running JSO transformations. With classes instead of lambdas I can reproduce this issue in my tests.

McMurat commented 10 months ago

Your commit "JS: fix unwrapping JS objects implemented in Java" fixed the issue for me. But i noticed that the compiled js file size in obfuscated mode is nearly 2x larger than the one which TeaVM 0.8.1 (or 0.9.0 ) was generating. Are there any recent changes which could cause this?

konsoletyper commented 10 months ago

But i noticed that the compiled js file size in obfuscated mode is nearly 2x larger than the one which TeaVM 0.8.1 (or 0.9.0 ) was generating

I don't know. If there was any way for me to reproduce it, I could try to find the reason.

konsoletyper commented 10 months ago

@McMurat I'm closing this issue, since I fixed it. If you have a separate issue with code size, you can create a new issue. There never was intention to make generated code bigger. Even more: according to my tests, TeaVM from master produces smaller obfuscated code compared to 0.9.0. It can be anything, without any details about your project I can't tell what's wrong. You can try to build without obfuscation and see what's the difference.

McMurat commented 10 months ago

@konsoletyper thank you very much for your help! I will look into it and open a new issue as soon as I have enough evidence to describe what's going on.