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

Colin Clark colinbdclark at gmail.com
Fri May 13 21:59:43 UTC 2011


This was a very informative and very, very long post. 

Everyone: here's Antranig's summary, in case you didn't have time to read to the bottom. It's important information:

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

Colin

On 2011-05-12, at 4:32 AM, Antranig Basman wrote:

> 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

---
Colin Clark
Technical Lead, Fluid Project
http://fluidproject.org




More information about the fluid-work mailing list