Exception handling, "notrycatch", debugging in IE, and new diagnostics

Antranig Basman antranig.basman at colorado.edu
Thu May 12 08:32:09 UTC 2011


Whilst dealing with our logging/JSON issues for FLUID-4197, I ran into a test which failed under IE8 
(UploaderCompatibilityTests.js) and was sunk into the full horror of how nasty IE debugging generally is. 
Given that IoC debugging is also generally nasty, the combination of the two issues was strongly 
unpleasant... despite getting an "object has no property xxxx" error it was entirely impossible to figure 
out, for example, even what line of code the problem was thrown from.

TRY-CATCH-FINALLY
=================

Having some experience of the terrain, I went for the "magic catch block" that I know lives within qunit.js 
to comment it out, since in the past, I had anecdotal evidence that removing try... catch blocks can make IE 
failures and test exceptions easier to pinpoint... I discovered that the code had been upgraded since I had 
last visited it, to include the following very interesting section...

		if ( config.notrycatch ) {
			this.callback.call(this.testEnvironment);
			return;
		}
		try {
			this.callback.call(this.testEnvironment);
		} catch(e) {

It's clear that the qunit framework author had run into similar issues - tracing this back to its source, it 
seems that adding the query parameter "notrycatch" to the URL of any qunit test page will automatically 
disable the try/catch block with no code hacking required. This is already well worth knowing.

After this I got to thinking... is this kind of insanity REALLY necessary? Going on an all-out trawl for 
information on exception tracing on IE, I discovered that it really is. The following guide, "3 painful ways 
to obtain a stack trace"

http://blog.yoursway.com/2009/07/3-painful-ways-to-obtain-stack-trace-in.html

looks like the result of pretty good research, and declares quite firmly that, given an Exception/Error 
object in IE, there is *NO* way to use it to yield a stack trace. What is worse - having actually CAUGHT the 
exception, whatever stack information the browser might ever have yielded to you has been PERMANENTLY 
DESTROYED. The guide explains that parts of this may be reconstructed by piecing together info from multiple 
catch sites, but as well as this being ridiculous, the crucial info, from the very top of the stack can 
never be recovered.

Doublechecking the "exception stripping code" that I ripped off for FluidDebugging.js from "emwendelin"
https://github.com/emwendelin/javascript-stacktrace/blob/master/stacktrace.js
confirms that yes - under IE, you can get your CURRENT stack information by use of the "callee" property - 
but once the stack has returned and the exception has been caught, it is destroyed for good.

Even Firefox is pretty awkward about yielding exception info in Firebug - which is the reason for our 
long-standing and rather odd behaviour inside fluid.fail() which rather than throwing a user exception which 
is worthless for debugging purposes, instead causes a language-level failure which will trip the debugger - 
but given the IE behaviour, there is only one and very unpalatable conclusion to draw....

        try-catch blocks SHOULD NOT be used in JavaScript code running in the browser!

This seems to reflect a fairly basic misunderstanding by browser authors as to the way exceptions are meant 
to be used (at least, reflected in the ways people are used to using them for some reliable purposes in 
Java, C++, etc.) - it seems clear that firstly, they are intended to be "unrecoverable" and to signal a 
logic failure (aka an assertion failure) - and the only reliable way to get full debugging info for a 
failure is to have a completely UNINTERRUPTED path from the point of the failure back down to the browser, 
with no try-catch blocks intervening.

However, this situation may not last forever on the browser, and may not even reflect the state of 
environments such as node.js on the server. So we really need to be able to have the moral effect of 
try-catch-finally together with the ability to disable it completely depending on environment.

Taking a leaf out of the qunit author's book, I was led to write the following function, the sight of which 
one would normally expect to excite abuse, hilarity and contempt:

     fluid.tryCatch = function(tryfun, catchfun, finallyfun) {
         finallyfun = finallyfun || fluid.identity;
         if (fluid.notrycatch) {
             var togo = tryfun();
             finallyfun();
             return togo;
         }
         else {
             try {
                 return tryfun();
             }
             catch (e) {
                 if (catchfun) {
                     catchfun(e);
                 }
                 else {
                     throw(e);
                 }
             }
             finally {
                 finallyfun();
             }
         }
     };


This function is now in Fluid.js... and is used by ALL sites in framework code which issue try blocks... 
being the Fluid Event system and 3-4 sites in IoC. Following the qunit author the framework responds to 
exactly the SAME URL parameter "notrycatch" that is recognised by qunit, giving us now a "one stop shop" for 
test cases in IE - for example,

file:///E:/Source/gits/infusion-master/src/webapp/tests/component-tests/uploader/html/UploaderCompatibility-test.html?notrycatch

will stick you right into the IE8 debugger at the failure line, no matter how deep the failure occurs.

Getting back to the original issue...

IoC DEBUGGING
=============

When working with Clayton last week, there was yet more brutal evidence of how opaque it can be to debug IoC 
issues, especially at a distance... and part of the issue is not unrelated to the previous point. The most 
suitable way in Java, for example, to supply extra failure semantics characterising what the framework 
thinks it is doing at some deeply nested callsite, is to piggy-back on try..catch blocks, which approach was 
used in the UniversalRuntimeException system that was used in RSF. Already being suspicious of exceptions in 
JavaScript, I'd held off implementing this, and the research above only cements the point - since catch 
blocks, ESPECIALLY in the crucial situations in IE where debug traces would be most helpful, cannot be used, 
we have to use a quite different system... which pushes information onto a custom stack BEFORE the point of 
failure, rather than tagging information down cascading catch blocks AFTER the failure. This system is now 
in FluidIoC.js, under the name of functions "fluid.pushActivity" and "fluid.wrapActivity". The crucial 
centre of the system indeed uses the fluid.tryCatch function listed above, and looks like this:

         root.activityStack = root.activityStack.concat(frames);
         return fluid.tryCatch(func, null, function() {
             root.activityStack.length -= frames.length;
         });

The "activity stack" is now available at all points of the framework code, and is queried from fluid.fail 
(if IoC is loaded) to tack contextual information on failure traces. Now rather than being just told that 
"something has failed" there will be a pretty intelligible trace of "while" context messages tracing through 
the intentions of the framework that brought it to that point - for example, here is a trace from one of the 
test cases in FluidIoC.js under the new system:

Wed May 11 2011 22:11:08 GMT-0600 (Mountain Daylight Time): ASSERTION FAILED: Component { typeName: 
"fluid.tests.circular.local" id: 12} at path "local" of parent { typeName: "fluid.tests.circular.strategy" 
id: 8} cannot be used for lookup since it is still in creation. Please reorganise your dependencies so that 
they no longer contain circular references
     while resolving context value {strategy}.local
     while invoking invoker with name fluid.tests.circular.eventBinder on component Object { 
typeName="fluid.tests.circular.engine", id=13, more...}
     while instantiating dependent component with name "engine" with record Object { 
type="fluid.tests.circular.engine"} as child of Object { typeName="fluid.tests.circular.strategy", id=8, 
more...}
     while expanding component options {engine}.swfUpload with record Object { marker={...}, 
value="{engine}.swfUpload", localRecord={...}} for component Object { typeName="fluid.tests.circular.local", 
id=12, more...}
     while instantiating dependent component with name "local" with record Object { 
type="fluid.tests.circular.local"} as child of Object { typeName="fluid.tests.circular.strategy", id=8, 
more...}

This looks much better in the debugger that as ASCII - since through upgrading the "fluid.fail"/"fluid.log" 
support for the original issue FLUID-4197, these objects that appear in the trace are actually 
clickable/expandable JSON or DOM views as natively supported by console.log on the "good browsers" we talked 
about this afternoon. But the crucial point is the five "while" clauses - as well as the headline message 
(which is all we got before) we can now see exactly what the framework was doing on the way to the failure 
site.

Reading up from the bottom,
i) it started to instantiate the subcomponent "local" of the "fluid.tests.circular.strategy" object
ii) it started to expand its options, and discovered a reference {engine}.swfUpload, which led it to
iii) immediately start on a sibling subcomponent (a "ginger instantiation"), component "engine"
iv) on which it found an invoker (invoked on construction) named "eventBinder"
v) in whose configuration it found a reference to "{strategy}.local", a circular reference to the component 
at i)

UPLOADER TESTS
==============

So... this is all relatively jolly. Leading back to the original cause, UploaderCompatibilityTests.js on 
IE8... with the tools mentioned before, it was then not too hard to find out that the cause was that it was 
using the "native" progressiveEnhancer for IE8, rather than an appropriately mocked testing instance - which 
given I don't have Flash installed, was falling back to the "singleFileUploader" which didn't have the 
expected event attached (we should make sure that fails more gracefully when we have a chance).


The main points of news again in short:

A) NEVER use unprotected try/catch in browser-side JavaScript, use the wrapper fluid.tryCatch instead
B) When in trouble in IE and/or a test case, add the "notrycatch" parameter to the URL
C) Watch out for new-style IoC debugging traces in the case of failure, read from the bottom up
D) Functional programming in JavaScript is GOOD, in the same sense that Alma Cogan ISN'T, given that all of 
the above stuff could be implemented with very little intrusion on the codebase even where it was previously 
using language-native facilities





More information about the fluid-work mailing list