JDK-8066932 : __noSuchMethod__ binds to this-object without proper guard
  • Type: Bug
  • Component: core-libs
  • Sub-Component: javax.script
  • Affected Version: 8u25
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_7
  • CPU: x86_64
  • Submitted: 2014-12-05
  • Updated: 2015-06-04
  • Resolved: 2014-12-11
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u40Fixed 9 b43Fixed
Description
FULL PRODUCT VERSION :
java version "1.8.0_25"
Java(TM) SE Runtime Environment (build 1.8.0_25-b18)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]

A DESCRIPTION OF THE PROBLEM :
If __noSuchMethod__ is used on a Prototype, it seems that the ScriptEngine executes the function with the first Object the script was called (ScriptEngien.eval). The second call to the ScriptEngine with invokeFunction the JavaScript object behaves like a singleton. I tested this with a JDK7 (Rhino) successfully, but on a JDK8 it did'nt work like expected.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Execute TestCase with JDK7, look for output
2. Execute TestCase with JDK8 compare outputs

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
868385821 == 868385821
675655832 == 675655832
2001526735 == 2001526735
1475988537 == 1475988537
1355734862 == 1355734862
ACTUAL -
1073533248 == 1073533248
576936864 != 1073533248
1653844940 != 1073533248
260840925 != 1073533248
1891502635 != 1073533248

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class PrototypeTest {
    private static final String TEST_SCRIPT =
"function proxyCall(realObj, mthName, args) {\r\n" +
"    var funcMap = {\r\n" +
"        'arg0': function (obj, id, arg) {\r\n" +
"            return obj[id]();\r\n" +
"        }\r\n" +
"    };\r\n" +
"    var argType = 'arg' + args.length;\r\n" +
"    if (!(argType in funcMap)) {\r\n" +
"        var funcStr = 'function(obj, id, arg){return obj[id](';\r\n" +
"        for (var i = 0, len = args.length; i < len; i++) {\r\n" +
"            funcStr += 'arg[' + i + ']';\r\n" +
"            if ((i + 1) < len) {\r\n" +
"                funcStr += ',';\r\n" +
"            }\r\n" +
"        }\r\n" +
"        funcStr += ');}';\r\n" +
"        funcMap[argType] = eval(funcStr);\r\n" +
"    }\r\n" +
"    return funcMap[argType](realObj, mthName, args);\r\n" +
"}\r\n" +
"function Dummy(obj) {\r\n" +
"    this.myObj = obj;\r\n" +
"}\r\n" +
"Dummy.prototype.hashCodeFunc = function () {\r\n" +
"    return this.myObj.hashCode();\r\n" +
"};\r\n" +
"Dummy.prototype.__noSuchMethod__ = function (name, args) {\r\n" +
"    var callArgs;\r\n" +
"    if (arguments.length == 2 && Array.isArray(args)) {\r\n" +
"        callArgs = args;\r\n" +
"    } else {\r\n" +
"        callArgs = Array.prototype.slice.call(arguments, 1);\r\n" +
"    }\r\n" +
"    return proxyCall(this.myObj, name, callArgs);\r\n" +
"};\r\n" +
"function handle(obj) {\r\n" +
"    var dum = new Dummy(obj);\r\n" +
"    var hc1 = dum.hashCodeFunc();\r\n" +
"    var hc2 = dum.hashCode();\r\n" +
"    print(hc1 + (hc1 == hc2 ? ' == ' : ' != ') + hc2 + '\\r\\n');\r\n" +
"}\r\n";

    public static void main(String[] args) {
        ScriptEngine se = new ScriptEngineManager().getEngineByName("JavaScript");
        try {
            se.eval(TEST_SCRIPT);
            for(int i=0; i<5; i++) {
                ((Invocable)se).invokeFunction("handle", new Object());
            }
        } catch (ScriptException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

---------- END SOURCE ----------


Comments
We're binding the this-object to the noSuchMethod invocation. This works for global object (also questionable with multiple globals) but obviously not with objects. Simpler code to reproduce: var i = 0; function Dummy(obj) { this.obj = obj; this.id = i++; } Dummy.prototype.__noSuchMethod__ = function (name, args) { print(this.obj, this.id); return this.obj.hashCode(); }; function test(obj) { var dum = new Dummy(obj); print(dum.hashCode()); } test(new java.lang.Object()); test(new java.lang.Object()); test(new java.lang.Object());
10-12-2014