-
Notifications
You must be signed in to change notification settings - Fork 863
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
fix #1300 Support ES2019 Function.toString() #1537
Conversation
I'd like to request not implementing this change for Java methods. I don't believe they need to conform to the spec since they come from additional libraries which are not part of javascript and are allowed to override the For example, js> java.util.Arrays.copyOf.toString()
function copyOf() {/*
float[] copyOf(float[],int)
int[] copyOf(int[],int)
byte[] copyOf(byte[],int)
long[] copyOf(long[],int)
char[] copyOf(char[],int)
boolean[] copyOf(boolean[],int)
double[] copyOf(double[],int)
java.lang.Object[] copyOf(java.lang.Object[],int)
short[] copyOf(short[],int)
java.lang.Object[] copyOf(java.lang.Object[],int,java.lang.Class)
*/}
js> i = java.util.Arrays['copyOf(int[],int)']([1.3, 2.7], 2)
[I@47eaca72
js> i[0]
1
js> i[1]
2
js> typeof i[0]
number
js> d = java.util.Arrays['copyOf(java.lang.Object[],int)']([1.3,2.7], 2)
[Ljava.lang.Object;@48f2bd5b
js> d[0]
1.3
js> d[1]
2.7
js> typeof d[0]
object
js> d[0].class
class java.lang.Double |
I too think this is a controversial change. I made the change for two reasons. First, reading the spec, it is probably correct to return
This is a user-defined case.
This is a built-in case.
And Java methods is supposed to be a case of 4. The second reason was that GraalJS returned
|
Is my understanding correct that it isn't necessarily I'd be fine with changing it for For example, in Chrome, I can do: > Math.random.toString()
<- 'function random() { [native code] }'
> Math.random.toString = () => "override"
<- () => "override"
> Math.random.toString()
<- 'override'
> Function.prototype.toString.call(Math.random)
<- 'function random() { [native code] }' I realize that is a user override, but I am just using it as an example that the What does GraalJS return for an overloaded method like Also, I'm not sure if it makes a difference, but it looks like both Chrome and GraalJS return single-line strings for |
I am not familiar with GraalJS either. By default, there seems to be a limit to the classes that can be used, and
|
I was trying to see if there was another way to easily enumerate the signatures of an overloaded method using javascript. I think I uncovered a bug, too. It is correct that NativeJavaClass presents itself as a function, because it is a constructor. However, js> Object.keys(java.math.BigInteger)
ZERO,valueOf,ONE,TEN,probablePrime,TWO
js> 'valueOf' in java.math.BigInteger
true
js> 'valueOf(long)' in java.math.BigInteger // not enumerable
true
js> Object.getOwnPropertyNames(java.math.BigInteger) // oops
js: uncaught JavaScript runtime exception: TypeError: Expected argument of type object, but instead had type function
at (unknown):57
js> typeof java.math.BigInteger
function
js> Function.prototype.toString(java.math.BigInteger)
function () {
[native code, arity=0]
}
js> Object.prototype.toString.call(java.math.BigInteger) // not [object Function], can a `JavaMethod` also be a special type?
[object JavaClass] I think the only other way to do this is getting messy with java reflection, something like this. function getSignatures(nativeJavaClass, methodName) {
return nativeJavaClass.__javaObject__.getMethods()
.filter(function(method) {
return (method.getName().equals(methodName)) &&
(method.getModifiers() & java.lang.reflect.Modifier.STATIC)
})
.map(String)
.join('\n')
} Of course, that would need to be slightly modified if you wanted to list instance methods on a NativeJavaObject instead of static methods on a NativeJavaClass, both of which are nicely handled currently by the javascript toString method on a NativeJavaMethod. |
Is this what you wanted to do?
This would be consistent with the spec if it were modified to set Although compatibility breakdown is a problem, I think more interoperability with GraalJS and Nashorn would increase the number of users. |
Oops, I thought I had caught and fixed that. I did have it correct in this comment #1537 (comment). Like I said, I think the I think probably the best solution would be to introduce a new object in the prototype chain that sits between the object and Function.prototype, and that object can implement the current toString method for NativeJavaMethod. Here is an example of how this could work. I know my Function.prototype.toString implementation here is too rudimentary, but it serves its purpose for this example: js> function test() {
> const result = []
> var v = java.lang.Integer.valueOf
> result.push(v.toString()) // 1 - current behavior
> Object.getPrototypeOf(v) === Function.prototype
> var p = new Function()
> var oldToString = v.toString
> Object.setPrototypeOf(v, p)
> Function.prototype.toString = function() { return 'function ' + this.name + '() { [native code] }'}
> result.push(v.toString()) // 2 - Full prototype chain in place calling replaced Function.prototype.toString
> p.toString = oldToString
> result.push(v.toString()) // 3 - Calling original toString behavior as a property of the new prototype object
> result.push(Function.prototype.toString.call(v)) // 4 - Returns "specification" output
> return result.join('\n--------------\n')
> }
js> test()
function valueOf() {/*
java.lang.Integer valueOf(java.lang.String,int)
java.lang.Integer valueOf(java.lang.String)
java.lang.Integer valueOf(int)
*/}
--------------
function valueOf() { [native code] }
--------------
function valueOf() {/*
java.lang.Integer valueOf(java.lang.String,int)
java.lang.Integer valueOf(java.lang.String)
java.lang.Integer valueOf(int)
*/}
--------------
function valueOf() { [native code] } This would not be unprecedented as GeneratorFunctions also have an intermediate prototype object. Although, in our implementation the GeneratorFunction prototype looks to have Object.prototype as its direct prototype rather than Function.prototype. This leads to the odd result of js> (function*(){}) instanceof Function
false Whereas in Chrome that returns true. |
// The Symbol to get a string of information about the Java being wrapped, or some object like __javaObject__
Symbol.javaXXX;
// For example
java[Symbol.javaXXX]; // An Object or a String, like [JavaPackage java]
java.util.HashMap[Symbol.javaXXX]; // [JavaClass java.util.HashMap]
(new java.util.HashMap())[Symbol.javaXXX]; // [JavaObject java.util.HashMap]
(new java.util.HashMap()).get[Symbol.javaXXX]; // [JavaMethod java.lang.Object get(java.lang.Object)] This would allow us to see information other than methods and would not interfere with the ECMA 262 specification. |
I'm not opposed to creating a java Symbol, but the section of the specification you are citing is specifically for the I could easily write a function that violates the spec in any javascript engine if it the spec applied to function f(i) { return i + 1 }
f.toString = () => "hidden"
f.toString() // returns "hidden"
Function.prototype.toString.call(f) // this is what matters to the specification The actual I believe it could work by defining the JavaMethodPrototype object in a similar manner to the Date.prototype object. Again, in Chrome function test() {
var d = new Date()
var p = Object.getPrototypeOf(d)
console.log(d.hasOwnProperty('toString'))
console.log(p.hasOwnProperty('toString'))
console.log(p === Date.prototype)
d.toString = () => 'unavailable'
console.log(Object.prototype.toString.call(d))
console.log(Date.prototype.toString.call(d))
console.log(d.toString())
}
> test()
false
true
true
[object Date]
Fri Jul 26 2024 11:07:11 GMT-0400 (Eastern Daylight Time)
unavailable Anyone who is relying on the string value of an unknown function in their program should be using From what I gather from the original proposal, the whole point of this change is that Additionally, if we do use a To use generators as an example, in Chrome (but not in Rhino!) > g = (function*(){})
<- ƒ* (){}
> g[Symbol.toStringTag]
<- 'GeneratorFunction'
> g.hasOwnProperty(Symbol.toStringTag)
<- false
> Object.getPrototypeOf(g).hasOwnProperty(Symbol.toStringTag)
<- true |
I am sorry that I do not understand your problem very well. But for now, the nature of the problem is completely different between a JavaScript engine overriding a build-in function and a JavaScript engine user overriding a build-in function. For example, if |
I'm not talking about overwriting Currently, I'm suggesting that,
Additionally,
Hopefully we can get some others to chime in with their opinion on this @gbrail @p-bakker @rbri . |
Just for my info: the main reason you want a particular .toString implementation is to be able to discover all overloaded variants of a java method by calling .toString and manually reading the output while developing, instead of for example checking the relevant JavaDoc or using reflection? |
If Java method should have other prototype from JS function, should Java string and JS string also have other prototype? |
Javascript has String objects, which we implement as instances of Java Strings in javascript land are instances of rhino/rhino/src/main/java/org/mozilla/javascript/NativeJavaObject.java Lines 158 to 161 in cc302b4
Edit: If for some reason it was desirable for java Strings to have special behavior for certain functions defined in Nothing about that would conflict with the specification because the |
More or less, yes. My main points with regard to NativeJavaMethod are:
|
I too recognize that the current implementation is useful and that this PR change will break compatibility. I have written in the comment why I included the commit on top of that. If you really want to keep it, I delete the commit. However, I think that Rhino's challenge is that specification violations will remain for convenience and compatibility. It's like the old Internet Explorer. And you say that // Firefox
document.createElement.toString()
// "function createElement() {
// [native code]
// }" I do not agree with the proposal to create |
Again, the part you linked to in the specification is speaking to the behavior of |
I haven't seen anything from ECMA 262 that says something like “it is recommended to override the toString for debugging purposes.”. Also, I am unaware of any runtime environment that overrides the specification. |
It would not be overriding the specification. A Likewise, you are looking in (emphasis mine)
An instance of a
So, the specification explicitly allows us to specify our own prototype for this sub-class of functions, and nowhere does it say "properties of built-in function objects shall not deviate from the behavior of the properties of the |
To reiterate, in order to be in full compliance with the spec,
|
It is too ad hoc to create a new |
Well, it doesn't have to only implement
We are not in violation of the spec because of what is returned by |
Is this about the current Rhino implementation? Or are we talking about after the introduction of It seems to me that your comment is a mix of before and after |
To summarize my opinion,
|
I agree with @tuchida that the current impl. violated the spec:
So, it we want EcmaScript compatibility, which I think is what we're after, the current .toString impl. on Java methods needs to go... In my personal opinion the use-case for the old implementation (basically reflection to discover all overloaded variants of a method) is rather slim and have no problem losing it. Instead, I rather see these issues fixed:
So you could do something like Object.getOwnPropertyDescriptors(java.math.BigInteger) and then find the info you're looking for |
Let's fix this in another issue.
|
Definitely, wasn't suggesting to do it as part of this case
Well, in Chrome Object.getOwnPropertyDescriptors(window) yields results as expected. If in Rhino such a call would yield propertyDescriptors for each individual overloaded method, I think it solves (at least a large chunk of) the issue @tonygermano has about discoverability of overloads, no? jsSo for Object.getOwnPropertyDescriptors(java.util.Arrays) would yield, amongst others, propertyDescriptors for:
The returnType would be missing though... If going the 'Symbol' route, I think that ought to be an opt-in feature, not something enabled by default |
Can you display overloaded constructors? Well, there seems to be a lot to think about. By the way, this is what the current implementation would look like.
The signatures of the eight constructors are not shown. |
Added #1549 as a spin-off from this discussion |
Sorry, I got really busy with other things and haven't been able to come back to this. I can live with this change for now, but I really don't like not having an easy alternate way to get the same information ready to replace it, and any replacement will be less intuitive. I apologize, I did find out that I was mistaken in thinking that However, an error is thrown for I have some additional thoughts, which are obviously still up for discussion. The majority of the logic to implement the spec should be here, which I'm pretty sure is where rhino/rhino/src/main/java/org/mozilla/javascript/BaseFunction.java Lines 320 to 325 in cc302b4
We should implement some of these abstract methods, and I don't think we need to call decompile on function types that we know won't decompile.
I have not searched the code base to see if there are any other classes which implement A A > Math.abs.toString()
<- 'function abs() { [native code] }'
> Math.abs.length
<- 1 Realizing that most of this was written well before Java 8, I wonder if some refactoring could be done to move some of the "Base" class functionality to interface default methods so that we can actually use the interfaces to tell what things are or what they support without losing all of the benefits of extending a base class. |
Thank you.
By the way, this is what happened in the JavaScript class. // Firefox
class C { constructor(a) {} }
C.toString()
// "class C { constructor(a) {} }"
Function.prototype.toString(C)
// "function () {
// [native code]
// }" It might be a good idea to consider the relationship between JavaScript classes and Java classes to determine the specifications. |
Firefox looks ok on my computer. To me this indicates that a user-defined class has a [[SourceText]] value. This is what I get in Firefox 128.0.2 Snap for Ubuntu class C { constructor(a) {} }
C.toString()
// "class C { constructor(a) {} }"
Function.prototype.toString.call(C)
// "class C { constructor(a) {} }" Edit: I had a typo. Chrome 126.0.6478.132 on ChromeOS looks the same. In both cases Object.getPrototypeOf(C) === Function.prototype
// true In the case of a |
I think it would be more like one of the Built-in objects that are also constructors. // Chrome
typeof String
// 'function'
Object.getPrototypeOf(String) === Function.prototype
// true
Function.prototype.toString.call(String)
// 'function String() { [native code] }' The main difference being that constructors which are instances of // Rhino
js> typeof java.lang.String // "function" means it's callable
function
js> Object.getPrototypeOf(java.lang.String) // probably ok
null
js> Function.prototype.toString.call(java.lang.String) // this should work because it's an object and callable
js: uncaught JavaScript runtime exception: TypeError: Method "toString" called on incompatible object (org.mozilla.javascript.NativeJavaClass is not an instance of org.mozilla.javascript.BaseFunction).
at (unknown):4 |
The reason I think it's ok that it doesn't have a prototype is because these are kind of special objects (Edit: I think the correct term may be "exotic" objects). For example, you can't call a js> java.lang.String.toString()
// js: Java class "java.lang.String" has no public instance field or method named "toString".
// at (unknown):2
js> java.lang.Integer.toString() // toString property exists, but not with that method signature
// js: Can't find method java.lang.Integer.toString().
// at (unknown):3
js> java.lang.Integer.toString // so helpful 😢
// function toString() {/*
// java.lang.String toString(int,int)
// java.lang.String toString(int)
// */}
js> String(java.lang.String)
// [JavaClass java.lang.String]
js> java.lang.String + 5
// [JavaClass java.lang.String]5
js> Object.setPrototypeOf(java.lang.String, Function.prototype)
// [JavaClass java.lang.String]
js> java.lang.String.toString
// js: Java class "java.lang.String" has no public instance field or method named "toString".
// at (unknown):9
js> Object.getPrototypeOf(java.lang.String) // didn't actually set
// null |
@tonygermano @p-bakker Do you like the idea of using // Firefox
console.log(document.body);
// <body> <-- expandable
var obj = { a: 1 };
console.log(obj);
// Object { a: 1 }
// These are not the same as the result of toString
document.body.toString(); // "[object HTMLBodyElement]"
obj.toString(); // "[object Object]" When rhino/rhino/src/main/java/org/mozilla/javascript/NativeConsole.java Lines 344 to 368 in cc302b4
|
Rhino doesn't expose console by default (except in the shell) and outside of the shell one needs to provide an implementation. Don't remember the details of providing an implements and whether the details of stringification of for example a function is within the realm of the part the the console implementation that Rhino provides, but if so, I guess that could be a good option |
ref. #1554 |
That was a very detailed discussion! Would one of you be willing to summarize this thread into a proposed action to take in either a modified version of this PR, or a new PR? |
From this comment, it seems that @tonygermano has tolerated this PR. I then proposed the idea of using |
ref. https://github.com/mozilla/rhino/actions/runs/10294765267/job/28493340102?pr=1537 |
Focusing on this PR, I think some of the points I made in #1537 (comment) still need to be addressed. Namely, For example, we could add this method to default boolean hostHasSourceTextAvailable() { return false; } Only the classes that do have the source available would need to override that method, and those are the only ones that we would need to decompile (which is a |
@tonygermano I would like to see that addressed in another Pull Request. |
Isn't the EcmaScript-defined internal And I think we already have such a global flag somewhere, to exclude all source. |
Isn't it the other way around, it passes on Java 17, but not on Java 11? Our unicode depends on the Java implementation/version and both EcmaScript and Java include the latest unicode standard upon request. As such, unicode behavior in Rhino on an older Java version won't be 100% standard compliant |
@gbrail Thanks! I misunderstood the reason for the failure. |
It looks function level to me... This is step 2 from the
And the definition from https://262.ecma-international.org/15.0/index.html#sec-hosthassourcetextavailable
I was assuming that all Callables have a [[SourceText]] internal slot, which is empty for "native code". My thought was that // maybe a different type? - "sequence of Unicode code points"
default Optional<String> getInternalSourceText() { return Optional.empty() }
// this one it actually refers to as a string
default Optional<String> getInternalInitialName() { return Optional.empty() } The bottom line is that |
I will leave the rest to @tonygermano. |
@tuchida I'm sorry, I did not mean to discourage you from working on this. If the goal of this PR is to make the function spec compliant, in reviewing I was finding places where it still was not spec compliant. I'd be happy to assist if you'd like to keep working on this. If anyone else decides to pick it up, I found another case that still isn't working. js> (function() {
"use strict";
let desc = Object.getOwnPropertyDescriptor(arguments, 'callee')
return Function.prototype.toString.call(desc.get)
})() > > > >
js: uncaught JavaScript runtime exception: TypeError: Cannot find default value for object.
at (unknown):9 (anonymous)
at (unknown):6 In chrome the output is |
Same here @tuchida, all feedback is just to get the correct thing implemented and I agree with you too not try to much in a single PR (and instead create follow-up cases where possible. I really appreciate all the fixes you contributed lately and hope you'll be willing to reopen this PR to get it over the finish! As for As such, I think our current Context.setGeneratingSource(boolean is sufficient in this area, as the default impl. Of the abstract operation ought to always return true |
@tonygermano the issue you described in #1537 (comment): is that new in this PR or an existing issue? Given the error you're getting, I wonder if this isn't a completely unrelated issue to the changes in this PR |
The runtime exception is not caused by this PR, but this is an example of a different class implementing There should be a test case for this class, even if the test is not currently passing. |
I've got it in my repo at branch func.prototype.string if you want to clone it from there. https://github.com/tonygermano/rhino/tree/func.prototype.tostring I did it a couple weeks ago and don't remember exactly how I did it, but I think there are similar instructions here https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally |
Closes #1300
I have changed the result of calling
toString
for the following functions as specified.Previously, the results were as follows.