javadelight / delight-graaljs-sandbox

A sandbox for executing JavaScript with Graal in Java
Other
44 stars 11 forks source link

JSArray is converted to Map when converted to Java object. #7

Closed ThisOldDog closed 2 years ago

ThisOldDog commented 3 years ago

Hi there, We tried to call custom Java functions in JS, but when the parameters were converted, we found that the array was converted to a Map. I found the relevant conversion process from the source code:

/**
 * org.javadelight:delight-graaljs-sandbox:jar:0.1.2
 * com.oracle.truffle.polyglot.ToHostNode#convertToObject
 */
private static Object convertToObject(Object value, PolyglotLanguageContext languageContext, InteropLibrary interop) {
    try {
        if (interop.isNull(value)) {
            return null;
        } else if (interop.isString(value)) {
            return interop.asString(value);
        } else if (interop.isBoolean(value)) {
            return interop.asBoolean(value);
        } else if (interop.isNumber(value)) {
            Object result = convertToNumber(value, interop);
            if (result != null) {
                return result;
            }
            // fallthrough
        } else if (interop.hasMembers(value)) {
            return asJavaObject(value, Map.class, null, false, languageContext);
        } else if (interop.hasArrayElements(value)) {
            return asJavaObject(value, List.class, null, false, languageContext);
        } else if (interop.isExecutable(value) || interop.isInstantiable(value)) {
            return asJavaObject(value, Function.class, null, false, languageContext);
        }
        return languageContext.asValue(value);
    } catch (UnsupportedMessageException e) {
        throw new AssertionError(e);
    }
}

The hasMembers method is called before the hasArrayElements method:

// com.oracle.truffle.js.runtime.builtins.JSClassGen.InteropLibraryExports.Uncached#hasMembers
public boolean hasMembers(Object receiver) {
    assert this.accepts(receiver) : "Invalid library usage. Library does not accept given receiver.";
    return JSClass.hasMembers(((DynamicObject) receiver) );
}

// com.oracle.truffle.js.runtime.builtins.JSClass#hasMembers
static boolean hasMembers(DynamicObject target) {
    return JSRuntime.isObject(target);
}

// com.oracle.truffle.js.runtime.JSRuntime#isObject(com.oracle.truffle.api.object.DynamicObject)
public static boolean isObject(DynamicObject vo) {
    ObjectType type = vo.getShape().getObjectType();
    return (type instanceof JSClass) && (type != Null.NULL_CLASS);
}

As you can see, JSArray inherits from JSClass: image

Therefore, JSArray will be converted into an empty Map(Although I can get the array elements by PolyglotMap.get("arrayIndex"), but this is very strange). Do you have any ideas about why this might happen?

Any help would be appreciated 😃

mxro commented 3 years ago

Hello @ThisOldDog - thank you for raising an issue and for the excellent description!

That is interesting! So the solution would be if these two if statements were reversed:

        } else if (interop.hasMembers(value)) {
            return asJavaObject(value, Map.class, null, false, languageContext);
        } else if (interop.hasArrayElements(value)) {
            return asJavaObject(value, List.class, null, false, languageContext);
        }

Maybe wortwhile to try to patch the Graaljs version for this? https://github.com/javadelight/delight-graaljs-sandbox/blob/master/pom.xml#L58

ThisOldDog commented 2 years ago

Thank you for your reply. I temporarily covered the code to solve the problem.😂