Skip to content

[Rhino] Setting (function () {}).__proto__ broken in recent version of HtmlUnit #291

@atnak

Description

@atnak

Problem in brief

Setting of (function () {}).__proto__ is a no-op in recent versions of HtmlUnit breaking some libraries.

Setting of new Object().__proto__ still works as expected.

This problem affects the Knockout library of at least v3.3.0 and the current version v3.5.1.

Details

When Rhino is running in ES6 mode, setting of _proto__ on a function object is denied by this condition in SpecialRef that only allows setting on objects. This behaviour is incompatible with major browsers (Chrome / FF / Edge / IE11) and breaks libraries that rely on this.

Test case

<html>
<head>
<script>
function test() {
	var proto = { foo: function (n) { console.log('foo'); } }
	proto.foo();

	var obj = function () {}
	console.log("Original __proto__:", obj.__proto__);

	obj.__proto__ = proto;
	console.log("Modified __proto__:", obj.__proto__);

	console.log("obj.foo:", obj.foo);

	try {
		obj.foo();
	} catch (e) {
		console.log(e);
	}
}
</script>
</head>
<body onload="test()">
</body>
</html>

Expected:

foo
Original __proto__: function () {[native code]}
Modified __proto__: ({foo: (function (n) { console.log("foo"); })})
obj.foo: (function (n) { console.log("foo"); })
foo

HtmlUnit 2.46.0 fails with the following:

foo
Original __proto__: function () {[native code]}
Modified __proto__: function () {[native code]}
obj.foo: net.sourceforge.htmlunit.corejs.javascript.Undefined@0
TypeError: Cannot find function foo in object function () {...}.

Suggested fix

The documentation for Function's __proto__ (Object.prototype__proto__) doesn't seem to make any distinction between the state of support for objects and function objects so it seems they should be equally supported, if supported at all.

Out of the possible values returned by typeof, it seems "function" and "object" are the only ones that make sense:

[undefined, null, true, 123, 123n, "abc", Symbol(), function () {}, {}].forEach(x => {
    try {
        var proto = {}
        var r = (x.__proto__ = proto);
        console.log(typeof x + ": returned=" + (r === proto) + " changed=" + (x.__proto__ === proto))
    } catch (e) {
        console.log(typeof x + ": " + e);
    }
})

/* Output of Chrome / FF:
undefined: TypeError: Cannot set property '__proto__' of undefined
object: TypeError: Cannot set property '__proto__' of null
boolean: returned=true changed=false
number: returned=true changed=false
bigint: returned=true changed=false
string: returned=true changed=false
symbol: returned=true changed=false
function: returned=true changed=true
object: returned=true changed=true
*/

(This can be run in HtmlUnit 2.46.0 by removing 123n (BigInt(123)) which is not supported.)

In lieu of this, my suggestion is for this condition in SpecialRef be changed as follows:

 if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
-    if ((value != null && !"object".equals(ScriptRuntime.typeof(value))) ||
-        !"object".equals(ScriptRuntime.typeof(target))) {
-        return Undefined.instance;
-    }
+    if (value != null && !"object".equals(ScriptRuntime.typeof(value))) {
+        return obj;
+    }
+    switch (ScriptRuntime.typeof(target)) {
+    case "object":
+    case "function":
+        break;
+    default:
+        return obj;
+    }
     target.setPrototype(obj);
 } else {
     target.setPrototype(obj);
 }

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions