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 <[email protected]> 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/
