Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to identify arguments object as Value? #34

Closed
provegard opened this issue Aug 13, 2018 · 8 comments
Closed

How to identify arguments object as Value? #34

provegard opened this issue Aug 13, 2018 · 8 comments
Assignees

Comments

@provegard
Copy link

I'm trying to migrate a big Nashorn application to GraalJS. One of the things the code does is convert a JavaScript value to a Scala/Java value. An Arguments object is detected and converted to an array in Nashorn like this:

case a: ScriptObjectMirror if a.getClassName == "Arguments" => extractArray(a)

I haven't succeeded in testing if a Value is Arguments. My test method looks like this:

public void acceptValue(Value v) {
    System.out.println("value = " + v);
    System.out.println("meta = " + v.getMetaObject());
    System.out.println("meta:type = " + v.getMetaObject().getMember("type"));
    System.out.println("meta:className = " + v.getMetaObject().getMember("className"));
    System.out.println("meta:description = " + v.getMetaObject().getMember("description"));
}

And I'm calling it like this:

context.eval("js", "(function () { main.acceptValue(arguments); })('a', 'b', 'c')");

This is the printout:

value = {0: "a", 1: "b", 2: "c", length: 3, callee: {...}, Symbol(Symbol.iterator): {...}}
meta = Object
meta:type = object
meta:className = Object
meta:description = {0: "a", 1: "b", 2: "c", length: 3, callee: {...}, Symbol(Symbol.iterator): {...}}

Sadly, neither type nor className are useful here.

Any suggestions?

@chumer
Copy link
Member

chumer commented Aug 15, 2018

I don't think you can detect whether an array is in fact the arguments array, but you may detect that it is an array.

This is how you can check for an array:

    public static void main(String[] args) {
        try(Context ctx = Context.newBuilder().arguments("js", new String[] {"arg0", "arg1"}).build()) {
            Value arguments = ctx.eval("js", "arguments");
            System.out.println("getMetaObject().toString(): " + arguments.getMetaObject().toString());
            System.out.println("hasArrayElements(): " + arguments.hasArrayElements());
            System.out.println("string array(): " + Arrays.toString(arguments.as(String[].class)));
        }
    }

This prints:

getMetaObject().toString(): Array
hasArrayElements(): true
string array(): [arg0, arg1]

I'd recommend to use hasArrayElements as it is language agnostic.

Does this help? (feel free to close if it does)

@provegard
Copy link
Author

What I'm after is the arguments variable that exists inside a JavaScript function. I modified your code:

        try(Context ctx = Context.create("js")) {
            Value arguments = ctx.eval("js", "(function () { return arguments; })('a', 'b', 'c')");
            System.out.println("getMetaObject().toString(): " + arguments.getMetaObject().toString());
            System.out.println("hasArrayElements(): " + arguments.hasArrayElements());
            System.out.println("string array(): " + Arrays.toString(arguments.as(String[].class)));
        }

It prints:

getMetaObject().toString(): Object
hasArrayElements(): false
Exception in thread "main" java.lang.ClassCastException: Cannot convert '{0: "a", 1: "b", 2: "c", length: 3, callee: {...}, Symbol(Symbol.iterator): {...}}'(language: JavaScript, type: Object) to Java type 'java.lang.String[]': Value must have array elements.

@provegard
Copy link
Author

I tested another variant:

        try(Context ctx = Context.create("js")) {
            Value arguments = ctx.eval("js", "(function () { return arguments; })('a', 'b', 'c')");
            ctx.getBindings("js").putMember("obj", arguments);
            System.out.println(ctx.eval("js", "Object.prototype.toString.call(obj)"));
        }

And it prints:

[object Arguments]

Hooray! :) Can I obtain that value in Java land without executing JS?

@chumer
Copy link
Member

chumer commented Aug 16, 2018

@woess Why does the arguments object not return true for HAS_SIZE? Is it not an array? Seems like a bug to me.

@woess
Copy link
Member

woess commented Aug 17, 2018

@chumer technically, an arguments object is not a JS array (Array.isArray(arguments) returns false). But arguably, it has a size and array elements, so HAS_SIZE should probably return true...

@woess
Copy link
Member

woess commented Aug 17, 2018

@provegard

Can I obtain that value in Java land without executing JS?

I'm afraid that's the only reliable way to identify if something is an arguments object currently. You can of course do something like this:

Value isArguments = ctx.eval("js", "(function(obj) {return Object.prototype.toString.call(obj) === '[object Arguments]';})");

boolean isArguments(Value obj) {
  return isArguments.execute(obj).asBoolean();
}

@provegard
Copy link
Author

@woess That is what I'm doing now, and it works well.

Feel free to close the issue unless you want to keep it open for the HAS_SIZE thing.

@woess
Copy link
Member

woess commented Aug 17, 2018

As of 8423934, HAS_SIZE/hasArrayElements() returns true for arguments objects.

@woess woess closed this as completed Aug 17, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants