Problem (?) with Nashorn management of Error prototype

David P. Caldwell david at code.davidpcaldwell.com
Fri Oct 3 16:20:21 UTC 2014


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