다음을 통해 공유


Run-time exception checking

One of our partners asked us how a .NET program can tell what the currently active “try” blocks are on the stack.  This seemed like a dubious thing to want to do, but regardless a colleague of mine whipped up some sample code that uses the StackTrace class and reflection to do this.  We were talking about the possible uses of this, and most of them seemed pretty evil.  Changing program behaviour based on dynamic program inspection can lead to programs that are hard to reason about and brittle due to violating abstraction boundaries and implicit coupling.  Not to mention the fact that in optimized builds, some data may be missing from the StackTrace due to in-lining or other optimizations.

However, in-process inspection mechanisms like the StackTrace class can be incredibly useful for diagnostics purposes.  Perhaps in certain situations, it would be valuable to be able to write assertions like “If I throw a FooException, it should be handled by somebody”. As you probably know, there is a lot of debate about the value of checked exceptions, but I’m not going to discuss that here, see the Artima interviews with Anders Hejlsberg and James Gosling for a start.  We all know that validating your assumptions with assertions is a critical part of writing quality software, especially for large systems.  So if you’re making some assumptions about exception behaviour, it seems logical to want to check those assumptions at run-time with assertions.  Assertions can also be useful for making you aware of changes in behaviour that might have an impact on an area you didn’t anticipate.  You could imagine more complex checks like “The only catch handler for System.Exception is the one I know about in the top loop of my application” (nothing is more annoying then having some library code swallow your exceptions).  Of course checking these sorts of things statically (with tools like FxCop) is generally preferable to run-time checks, but our managed static analysis tools are still quite primitive, and it is very difficult to do complete static analysis in the presence of dynamic control flow mechanisms like reflection and delegates.

Anyway, here is some sample code that shows how you could write such assertions (I apologize for the lack of syntax highlighting – this new community server software doesn’t properly paste formatted text).  I’m still not convinced this is necessarily a good idea, but it seems to have some intriguing possibilities nonetheless. 

// Get a list of all the try clauses that are active on the stack that would
// catch exceptions of the specified type.
static public List<ExceptionHandlingClause> GetActiveTryClauses(Type exType)
{
List<ExceptionHandlingClause> activeTryClauses =
new List<ExceptionHandlingClause>();

  StackTrace stackTrace = new StackTrace();
StackFrame[] frames = stackTrace.GetFrames();

  // Start at 1 to skip the GetActiveTryClauses frame.
for (int i = 1; i < frames.Length; i++)
{
StackFrame frame = frames[i];
MethodBase method = frame.GetMethod();
MethodBody body = method.GetMethodBody();

// Only consider methods that have an IL body.
if (body != null)
{
IList<ExceptionHandlingClause> ehClauses = body.ExceptionHandlingClauses;
foreach (ExceptionHandlingClause ehClause in ehClauses)
{
if (ehClause.Flags == ExceptionHandlingClauseOptions.Clause)
{
// Only consider clauses which are active on the current stack
int offsetInFrame = frame.GetILOffset();
int tryStartOffset = ehClause.TryOffset;
int tryEndOffset = tryStartOffset + ehClause.TryLength;

if ((offsetInFrame >= tryStartOffset) && (offsetInFrame < tryEndOffset))
{
// If desired, only collect clauses that would catch the specified type
if (exType == null || ehClause.CatchType.IsAssignableFrom(exType))
{
activeTryClauses.Add(ehClause);
}
}
}
}
}
}

return activeTryClauses;
}

public static bool IsCaught(System.Type type)
{
List<ExceptionHandlingClause> handlers = GetActiveTryClauses(type);
return (handlers.Count > 0);
}

This allows you to write simple checks like the following:

// Ensure someone will handle any IO failure here
Debug.Assert(TryClauseInfo.IsCaught(typeof(System.IO.IOException)));

// Ensure no-one is catching all exceptions
Debug.Assert(!TryClauseInfo.IsCaught(typeof(System.Exception)));

Or you could even write more complex checks based on the ExceptionHandlingClause information. 

What do you think?  Are there situations in your applications where you could get value out of using assertions like this? Can you think of any other uses for this code that wouldn't be totally evil?

Comments

  • Anonymous
    April 30, 2005
    This is an interesting idea, but couldn't it yield false positives if user exception filters are involved? A filter could narrow the exceptions caught in a particular catch block such that even if the exception type matches, it still might not be caught, i.e. "Catch e As IOException When False". Still, an interesting use of reflection.
  • Anonymous
    May 01, 2005
    re: "I apologize for the lack of syntax highlighting – this new community server software doesn’t properly paste formatted text"

    And it probably never will. The VS editor is a rich-text control that only fills the clipboard with RTF and Text. What many bloggers do is paste this into Word (which favors RTF) and then re-copy the text into Community Server. Word uses many clipboard formats - including HTML - which the editable content portion of CS will favor.
  • Anonymous
    May 01, 2005
    The comment has been removed
  • Anonymous
    May 02, 2005
    It's a bit of an understatement to say:

    "it is very difficult to do complete static analysis in the presence of dynamic control flow mechanisms like reflection and delegates."

    In fact, it's provably impossible!