8000 Make wrapped Java object not a JS array/map when static type is not list/map by ZZZank · Pull Request #1897 · mozilla/rhino · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Make wrapped Java object not a JS array/map when static type is not list/map #1897

New issue
Draft
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

ZZZank
Copy link
Contributor
@ZZZank ZZZank commented May 9, 2025

Fixes #1885

@ZZZank ZZZank marked this pull request as draft May 9, 2025 16:59
@ZZZank
Copy link
Contributor Author
ZZZank commented May 12, 2025

Hi @rPraml , I have a question related to SecurityControllerTest, and git commit history said you were the author

For an interface and implementation like this:

public interface SomeInterface {
    String foo();
}
_
public class SomeClass extends ArrayList<String> implements SomeInterface {
    @Override
    public String foo() { return "FOO"; }

    public String bar() { return "BAR"; }
}

When the access to SomeClass is restricted, SecurityControllerTest assume that SomeClass#size() (inherited from ArrayList) is still visible, but SomeClass#bar(), also a method only present in implementation, is not visible, and this really confuses me. So can you explain the intention of such design?

(Also SecurityControllerTest is the only failing test for changes in this PR, I want to make it pass)

@rPraml
Copy link
Contributor
rPraml commented May 12, 2025

Hello @ZZZank

SecurityControllerTest...

I hope that we will soon make the decision and remove all the SecurityController workarounds ;)

When the access to SomeClass is restricted, SecurityControllerTest assume that SomeClass#size() (inherited from ArrayList) is still visible, but SomeClass#bar(), also a method only present in implementation, is not visible, and this really confuses me. So can you explain the intention of such design?

This is AFAIK common design in java.

For example if you have a java.nio.file.Path variable or a RandomGenerator in your java code, you can access all methods exposed by this interface, but you cannot access methods on the concrete implementation. You must explicitly cast it. And this may be restricted because the impl resides in the sun.misc or jdk.internal package. Especially, when there is a module-info.java - it is not possible (and this has nothing to do with security controller, yet). Is this example clear enough to show what I would like to address with this test?

I understand, what you would achieve with your PR, but i fear, it will break lots of tests, especially where generic containers (that are not lists or maps) are in place.

Take the following test: It passes on develop, but fails on your branch, because AtomicReference.get() has as typeHint Object and the wrapFactory ignores the map. This may happen to all custom POJOs which may only return Object (either through type erasure, or because they are so designed)

    @Test
    public void accessMapInAtomicRef() {
        AtomicReference<Map<String, String>> reference = new AtomicReference<>();
        Map<String, String> map = new HashMap<>();
        map.put("a", "a");
        reference.set(map);

        assertEquals("a", runScriptAsString("value.get()['a']", reference, true));
        assertEquals("a", runScriptAsString("value.get().a", reference, true));
    }

Also SecurityControllerTest is the only failing test

now we have two ;) - please check, if the above is a legal use case (I think so)

Roland

@rPraml
Copy link
Contributor
rPraml commented May 13, 2025

I thought about this change again.

The problem here is, that js is "duck typed" and that concept is also adapted to the wrap-factory.

Let us consider the following example

class HybridListMap implements List, Map { // I know, not really possible, because interfaces clashes
}

Javascript looks only on the type of the object:

  • If it looks like a map, it must be a map...
  • If it looks like a list, it must be a list...

In java you can have a POJO, that has a Map getAsMap() { return hybrid; } and List getAsList() { return hybrid; }
Unfortunately, too little attention is paid to the return type.

This PR would fix this. (But i fear it will break lot of existing code) and PR #1853 will also improve type handling

What we need now, is an improved cast. See ZZZank#3
This would allow us to cast objects in Js like in Java and may solve the one or other use case.

Refering to #1885, maybe an explicit cast (Example)(Example.get()) could help in your case...
(Explicit cast should mean: Do not use duck typing)
All of this doesn't really feel right yet, but I wanted to share my thoughts, maybe you have other ideas

If we merge this PR, we should hide this functionality behind a flag (e.g. FEATURE_USE_STRICT_JAVA_TYPES), because I really think, that it will break things.

@rPraml
Copy link
Contributor
rPraml commented May 13, 2025

I tried an other aproach about the cast issue in #1902 - but I think we need help from one of the maintainers here, what the next steps could be

@ZZZank
Copy link
Contributor Author
ZZZank commented May 14, 2025

I guess I didn't explain my question clearly enough. What confuses me is actually not implementation-only methods being not accessible when static type does not expose the method.

There're two implementation-only methods used in SecurityControllerTest, SomeClass#size() and SomeClass#bar(), both of which not present in the static type SomeInterface. (The only method present in SomeInterface is String foo();)

"f = new com.example.securitytest.SomeFactory();\n"
+ "var i = f.create();\n"
+ "i.size();\n"
+ "i.bar();";

The strange thing here is that the test assume that SomeClass#size() is always accessible, even when access to the implementation class SomeClass is restricted. On the other hand, the test assume that SomeClass#bar() is not accessible in the same test case.

try {
// in restricted scope, we expect an EcmaError
runScript(script, RESTRICT_IMPL_ACCESS);
fail("EcmaError expected");
} catch (EcmaError ee) {
assertEquals("TypeError: Cannot find function bar in object []. (#4)", ee.getMessage());
}

The expected error is Cannot find function bar, so it assumes that .size() is still accessible, which does not seem to be right from my perspective.

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

Successfully merging this pull request may close these issues.

Java object will sometimes be treated as an array/map when it should not be
2 participants
0