Problem (?) with Nashorn management of Error prototype
Jim Laskey (Oracle)
james.laskey at oracle.com
Fri Oct 3 16:32:28 UTC 2014
Just responding quickly to let you know we will look into this but the 5 of us are all away till Monday with JavaOne and much needed breaks.
Cheers
-- Jim
Sent from Jim's iPhone
> On Oct 3, 2014, at 9:20 AM, David P. Caldwell <david at code.davidpcaldwell.com> wrote:
>
> P.S. on re-reading it occurs to me that it's possible I am crossing
> Nashorn-internal code that calls Object.freeze on the Error property
> of the global object, or something like that -- this is also
> consistent with my symptom of not being able to add properties to my
> replacement Error.
>
> On Fri, Oct 3, 2014 at 12:15 PM, David P. Caldwell
> <david at code.davidpcaldwell.com> wrote:
>> So I have a big library of Rhino-compatible (and browser-compatible)
>> code and have recently been working through the processes (debugger,
>> profiler) and code changes necessary to get it working with Nashorn.
>>
>> A bit of background: the project probably differs from the typical
>> early Nashorn use case in that it heavily manipulates scopes and
>> targeting (i.e., 'this') in order to achieve its modularization goals.
>> Much of this is abstracted into a "loader" layer whose innermost
>> Nashorn invocation is the following code:
>>
>> this.compile = function(name,code,scope,target) {
>> var compiled =
>> Context.getContext().compileScript(toSource(name,code),scope);
>> return Packages.jdk.nashorn.internal.runtime.ScriptRuntime.apply(compiled,target);
>> };
>>
>> Yes, internal interfaces, I've been warned, moving on ... :)
>>
>> The name "compile" is deceptive; it's actually the name of the
>> strategy. (I had tried several, and they are still in the code, and
>> selected at runtime via an environment variable. "compile" is the
>> default. Feel free to comment.) The function would, if it were
>> standalone, probably be called something like "run" or "load."
>> toSource() calls the Source constructor or factory method, depending
>> which is present (looks like that changed in early 1.8.0 JDKs).
>>
>> Here's the full source file:
>> https://bitbucket.org/davidpcaldwell/slime/src/d04bd814156d4e4cc520492e65876cf1235a55e8/loader/rhino/nashorn.js
>>
>> For a while, this barely worked with Nashorn (requiring workarounds)
>> because somehow with complicated scope chains Nashorn would get
>> confused and variables from outer scopes would be missing inside inner
>> scopes, particularly when Nashorn had been re-entered (i.e., the
>> apply() call from which the inner scope was being executed was
>> different from the apply() call in which the outer scope was
>> executed).
>>
>> I vaguely wrote to the list about this but I was never able to
>> reproduce the problem in a way that I could present or was small, and
>> then it was somehow fixed by the u20 release of JDK 1.8, and suddenly
>> everything worked.
>>
>> Except objects that have Error in the prototype chain. They still have
>> strange behavior, and I've been poking at it for some time, and have a
>> few guesses about it.
>>
>> The good news is, I have an easy way to reproduce it (instructions at
>> end of this message). The bad news is, it involves my whole JavaScript
>> shell because when I tried a minimal wrapper everything worked. The
>> good news is, I'm confident it's not a bug in my code, because:
>>
>> 1. The loader layer abstracts the differences between the four major
>> browsers, Rhino, and Nashorn, and everything except errors works the
>> same in all six, and errors work the same in the first five.
>>
>> 2. When I replace the Error global object with my own, the code works.
>> So it's something having to do with "your" Error object, or (my guess)
>> some conditional code you have somewhere inside Nashorn that checks
>> for it.
>>
>> My guess is that there is some optimization somewhere that walks the
>> prototype chain and checks for Error. I think this because I tried a
>> number of strategies for working around this:
>>
>> * Using Object.create
>> * Using an intermediary function ErrorType that had new Error() as its
>> prototype, and using new ErrorType() as the prototype for my function
>>
>> ... and they all had the same result if Error was anywhere in the
>> prototype chain of my error. But using a different prototype
>> (basically, writing my own Error) worked, also in several different
>> configurations.
>>
>> Here's the code that fails (ignore the loader scaffolding):
>>
>> $exports.Error.Type = function(name) {
>> var rv = function(message,properties) {
>> if (this instanceof arguments.callee) {
>> this.name = name;
>> this.message = message;
>> for (var x in properties) {
>> this[x] = properties[x];
>> }
>> } else {
>> return new arguments.callee(message);
>> }
>> };
>> rv.prototype = new Error();
>> return rv;
>> };
>>
>> Here's some test code that exercises it:
>>
>> var Unimplemented = jsh.js.Error.Type("Unimplemented");
>>
>> try {
>> throw new Unimplemented("Not implemented.");
>> } catch (e) {
>> jsh.shell.echo("toString: " + e.toString());
>> jsh.shell.echo("Error?: " + Boolean(e instanceof Error));
>> jsh.shell.echo("Unimplemented?: " + Boolean(e instanceof Unimplemented));
>> jsh.shell.echo("name: " + e.name);
>> jsh.shell.echo("message: " + e.message);
>> jsh.shell.echo("stack: " + e.stack);
>> }
>>
>> Here's the output if I use my own global Error object. That object is
>> implemented at the top of nashorn.js (same file as above):
>> https://bitbucket.org/davidpcaldwell/slime/src/d04bd814156d4e4cc520492e65876cf1235a55e8/loader/rhino/nashorn.js
>>
>> toString: Unimplemented: Not implemented.
>> Error?: true
>> Unimplemented?: true
>> name: Unimplemented
>> message: Not implemented.
>> stack: at rhino/nashorn.js:35
>> at /Users/dcaldwell/tmp/nashorn.error/jsh/src/jsh/test/manual/errortype.jsh.js:11
>> at rhino/nashorn.js:131
>> at rhino/nashorn.js:173
>> at rhino/nashorn.js:203
>> at rhino/literal.js:65
>> at rhino/literal.js#36:9<eval>@6:132
>> at rhino/literal.js#36:9<eval>@6:217
>> at /Users/dcaldwell/tmp/nashorn.error/jsh/script/jsh/jsh.js#111:14<eval>@10:91
>> at /Users/dcaldwell/tmp/nashorn.error/jsh/script/jsh/jsh.js:252
>>
>> That is what is intended. The output is roughly the same as that for
>> all four major browsers and for Rhino.
>>
>> Here's the output if I don't replace the global Error object:
>>
>> toString: Error
>> Error?: true
>> Unimplemented?: false
>> name: Error
>> message:
>> stack:
>>
>> So that's where I am at the moment. Basically, it looks like I'm
>> getting the error subtype's prototype as my error, no matter what I
>> do.
>>
>> A few comments:
>>
>> * My workaround *mostly* works. The main problems are, a.) I need to
>> enumerate all my own error subtypes like TypeError or else TypeError
>> objects are not instanceof Error. On the other hand, if I do that, b.)
>> TypeErrors (presumably other errors, also) thrown from Nashorn itself
>> are not instanceof TypeError or Error. So it's a little messy.
>>
>> * I haven't looked at the Nashorn source code surrounding any of this;
>> I've been debugging it as a black box. (Obviously the fact that I am
>> using ScriptRuntime, Context and Source means I've been in there a
>> little bit, though. I have not built it myself.)
>>
>> * This may be a red herring, but developing with NetBeans, if I step
>> through the $exports.Error.Type code (using a patched copy; NetBeans
>> Nashorn support also isn't good enough yet to run my shell, though it
>> becomes minimally usable with a little bit of patching; see
>> https://netbeans.org/bugzilla/show_bug.cgi?id=247441), it is *very*
>> confused. Inside the error subtype constructor (called "rv" above),
>> the debugger (but not the code!) thinks that the global object is
>> "this" (even when executing via new). But when the code executes, it
>> does *not* treat the global object as "this." So don't know what the
>> problem is here, but the whole thing smells funny.
>>
>> * My test environment is JDK 1.8.0u20. OS X Mavericks, though I can't
>> imagine that matters.
>>
>> * Possibly unrelated, but another strange hard-coding symptom I seemed
>> to have with Error is that if I assign properties to *my* replacement
>> Error constructor -- not to Error objects, but to the constructor (I
>> tried to use Error.nashorn to act as a conditional flag) -- those
>> properties are invisible; not enumerable or accessible. But I haven't
>> investigated this as thoroughly and it might be something dumb I'm
>> doing; just reporting in case it rings a bell. Possibly the Error
>> property of the global scope is used *by name* somewhere and handled
>> specially, because that one symptom still seemed to impact my Error
>> object even when I replaced yours.
>>
>> Anyway, I know this was long but I wanted to do my due diligence and
>> communicate all of it. Here's how to reproduce, starting in an empty
>> directory:
>>
>> 1. Build the shell to a subdirectory called "jsh":
>> jrunscript -e "load('https://bitbucket.org/davidpcaldwell/slime/raw/d04bd81/jsh/etc/jrunscript.js?relative=./install.jrunscript.js')"
>> jsh
>>
>> 2. To run the test case with my workaround enabled:
>> java -jar jsh/jsh.jar jsh/src/jsh/test/manual/errortype.jsh.js
>>
>> 3. To run the test case with my workaround disabled, exposing the
>> error (on a UNIX-like system; translate for Windows as necessary):
>> env DISABLE_NASHORN_ERROR_HACK=true java -jar jsh/jsh.jar
>> jsh/src/jsh/test/manual/errortype.jsh.js
>>
>> Hopefully it's something simple. :)
>>
>> -- David P. Caldwell
>> http://www.davidpcaldwell.com/
More information about the nashorn-dev
mailing list