Ethereal-dev: [Ethereal-dev] Exception bugs
Note: This archive is from the project's previous web site, ethereal.com. This list is no longer active.
From: Richard van der Hoff <richardv@xxxxxxxxxxxxx>
Date: Thu, 28 Jul 2005 16:22:45 +0100
Hi, I've been trying to make ethereal's exception handling work, and, in particular, make use of a FINALLY block. As a Java programmer, I expected a FINALLY block would /always/ be executed. This doesn't seem to be the case - indeed, there doesn't seem to be any difference between a FINALLY block and the end of a TRY block in the current implementation. I then discovered that there aren't any dissectors using FINALLY at the moment. Perhaps this is because it doesn't work :). Going on to think about how this could be fixed, I also noticed a few other things which jar (if you'll excuse the pun) with my Java-bred expectations. In summary: - FINALLY blocks not executed on exceptions (caught, uncaught, or (re)thrown from a CATCH block) - Exceptions not caught by a CATCH block aren't propagated to the parent TRY, CATCH - instead they are just dropped. Hence unexpected exceptions are sometimes ignored. - throwing a new exception from a CATCH or FINALLY block re-enters the CATCH block; this could produce an infinite loop. I've attached a patch, exntest.patch, which adds a small stand-alone test program which aims to test some of the exception functionality, and exposes some of these problems. The uselessness of FINALLY, in particular, seemed worth fixing, and I think that dropping unexpected exceptions is also quite dangerous. I've therefore created a second patch, exceptions.patch, which reworks the macros in exceptions.h to address the above problems. I'm sorry it's a bit of a rewrite - however, fixing the existing code looked pretty tough. The only problem with this is that it exposes a bug in packet-frame.c ... so the third and final patch here, frame.patch, fixes that. Comments are welcome on the patches - or even my interpretation of how exception-handling ought to work. I'm away from my desk for the next few days, however, so apologies if I don't reply immediately. Thanks, Richard -- Richard van der Hoff <richardv@xxxxxxxxxxxxx> Systems Analyst Tel: +44 (0) 845 666 7778 http://www.mxtelecom.com
Index: epan/exceptions.h =================================================================== RCS file: /cvs/ethereal/epan/exceptions.h,v retrieving revision 1.1.1.2 retrieving revision 1.5 diff -u -r1.1.1.2 -r1.5 --- epan/exceptions.h 4 Jul 2005 18:17:28 -0000 1.1.1.2 +++ epan/exceptions.h 28 Jul 2005 14:49:23 -0000 1.5 @@ -66,23 +66,31 @@ * This is really something like: * * { - * x = setjmp() + * caught = FALSE: + * x = setjmp(); * if (x == 0) { * <TRY code> * } - * else if (x == 1) { + * if (!caught && x == 1) { + * caught = TRUE; * <CATCH(1) code> * } - * else if (x == 2) { + * if (!caught && x == 2) { + * caught = TRUE; * <CATCH(2) code> * } - * else if (x == 3 || x == 4) { + * if (!caught && (x == 3 || x == 4)) { + * caught = TRUE; * <CATCH2(3,4) code> * } - * else { - * <CATCH_ALL code> { + * if (!caught && x != 0) { + * caught = TRUE; + * <CATCH_ALL code> * } * <FINALLY code> + * if(!caught) { + * RETHROW(x) + * } * }<ENDTRY tag> * * All CATCH's must precede a CATCH_ALL. @@ -110,40 +118,65 @@ * CLEANUP_CB_CALL_AND_POP */ +/* we do up to three passes through the bit of code after except_try_push(), + * and except_state is used to keep track of where we are. + */ +#define EXCEPT_CAUGHT 1 /* exception has been caught, no need to rethrow at + * END_TRY */ +#define EXCEPT_RETHROWN 2 /* the exception was rethrown from a CATCH + * block. Don't reenter the CATCH blocks, but do + * execute FINALLY and rethrow at END_TRY */ + +#define EXCEPT_FINALLY 4 /* we've entered the FINALLY block - don't allow + * RETHROW, and don't reenter FINALLY if a + * different exception is thrown */ #define TRY \ {\ except_t *exc; \ + volatile int except_state = 0; \ static const except_id_t catch_spec[] = { \ { XCEPT_GROUP_ETHEREAL, XCEPT_CODE_ANY } }; \ except_try_push(catch_spec, 1, &exc); \ - if (exc == 0) { \ + \ + if(except_state & EXCEPT_CAUGHT) \ + except_state |= EXCEPT_RETHROWN; \ + except_state &= ~EXCEPT_CAUGHT; \ + \ + if (except_state == 0 && exc == 0) \ /* user's code goes here */ #define ENDTRY \ - } \ + /* rethrow the exception if necessary */ \ + if(!(except_state&EXCEPT_CAUGHT) && exc != 0) \ + except_rethrow(exc); \ except_try_pop();\ } +/* the (except_state |= EXCEPT_CAUGHT) in the below is a way of setting + * except_state before the user's code, without disrupting the user's code if + * it's a one-liner. + */ #define CATCH(x) \ - } \ - else if (exc->except_id.except_code == (x)) { \ + if (except_state == 0 && exc != 0 && exc->except_id.except_code == (x) && \ + (except_state |= EXCEPT_CAUGHT)) \ /* user's code goes here */ #define CATCH2(x,y) \ - } \ - else if (exc->except_id.except_code == (x) || exc->except_id.except_code == (y)) { \ + if (except_state == 0 && exc != 0 && \ + (exc->except_id.except_code == (x) || exc->except_id.except_code == (y)) && \ + (except_state|=EXCEPT_CAUGHT)) \ /* user's code goes here */ #define CATCH_ALL \ - } \ - else { \ + if (except_state == 0 && exc != 0 && \ + (except_state|=EXCEPT_CAUGHT)) \ /* user's code goes here */ + #define FINALLY \ - } \ - { \ + if( !(except_state & EXCEPT_FINALLY) && (except_state|=EXCEPT_FINALLY)) \ /* user's code goes here */ #define THROW(x) \ @@ -154,7 +187,24 @@ #define GET_MESSAGE except_message(exc) -#define RETHROW except_rethrow(exc) +#define RETHROW \ + { \ + /* check we're in a catch block */ \ + g_assert(except_state == EXCEPT_CAUGHT); \ + /* we can't use except_rethrow here, as that pops a catch block \ + * off the stack, and we don't want to do that, because we want to \ + * excecute the FINALLY {} block first. \ + * except_throw doesn't provide an interface to rethrow an existing \ + * exception; however, longjmping back to except_try_push() has the \ + * desired effect. \ + * \ + * Note also that THROW and RETHROW should provide much the same \ + * functionality in terms of which blocks to enter, so any messing \ + * about with except_state in here would indicate that THROW is \ + * doing the wrong thing. \ + */ \ + longjmp(except_ch.except_jmp,1); \ + } #define EXCEPT_CODE except_code(exc)
Index: epan/Makefile.am =================================================================== RCS file: /cvs/ethereal/epan/Makefile.am,v retrieving revision 1.1.1.7 retrieving revision 1.8 diff -u -r1.1.1.7 -r1.8 --- epan/Makefile.am 4 Jul 2005 18:17:28 -0000 1.1.1.7 +++ epan/Makefile.am 27 Jul 2005 22:42:28 -0000 1.8 @@ -54,6 +54,7 @@ Makefile.common \ Makefile.nmake \ tvbtest.c \ + exntest.c \ doxygen.cfg.in CLEANFILES = \ @@ -68,7 +69,12 @@ libethereal_la_DEPENDENCIES = @G_ASCII_STRTOULL_LO@ @INET_ATON_LO@ @INET_PTON_LO@ @INET_NTOP_LO@ dfilter/libdfilter.la ftypes/libftypes.la dissectors/libdissectors.la tvbtest: tvbtest.o tvbuff.o except.o strutil.o - $(LINK) -o tvbtest tvbtest.o tvbuff.o except.o strutil.o `glib-config --libs` + $(LINK) $^ $(GLIB_LIBS) -lz + +exntest: exntest.o except.o + $(LINK) $^ $(GLIB_LIBS) + +tvbtest.o exntest.o: exceptions.h if HAVE_PLUGINS Index: epan/exntest.c =================================================================== RCS file: epan/exntest.c diff -N epan/exntest.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ epan/exntest.c 28 Jul 2005 14:37:14 -0000 @@ -0,0 +1,202 @@ +/* Standalone program to test functionality of exceptions. + * + * $Id: exntest.c,v 1.1 2005/07/27 22:42:28 richardv Exp $ + * + * Copyright (c) 2004 MX Telecom Ltd. <richardv@xxxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <stdio.h> +#include <glib.h> +#include "exceptions.h" + +gboolean failed = FALSE; + +void +run_tests(void) +{ + volatile unsigned int ex_thrown, finally_called; + + /* check that the right catch, and the finally, are called, on exception */ + ex_thrown = finally_called = 0; + TRY { + THROW(BoundsError); + } + CATCH(BoundsError) { + ex_thrown++; + } + CATCH(ReportedBoundsError) { + printf("01: Caught wrong exception: ReportedBoundsError\n"); + failed = TRUE; + } + CATCH_ALL { + printf("01: Caught wrong exception: %lu\n", exc->except_id.except_code); + failed = TRUE; + } + FINALLY { + finally_called ++; + } + ENDTRY; + + if (ex_thrown != 1) { + printf("01: %u BoundsErrors (not 1) on caught exception\n", ex_thrown); + failed = TRUE; + } + + if (finally_called != 1) { + printf("01: FINALLY called %u times (not 1) on caught exception\n", finally_called); + failed = TRUE; + } + + + /* check that no catch at all is called when there is no exn */ + ex_thrown = finally_called = 0; + TRY { + } + CATCH(BoundsError) { + printf("02: Caught wrong exception: BoundsError\n"); + failed = TRUE; + } + CATCH(ReportedBoundsError) { + printf("02: Caught wrong exception: ReportedBoundsError\n"); + failed = TRUE; + } + CATCH_ALL { + printf("02: Caught wrong exception: %lu\n", exc->except_id.except_code); + failed = TRUE; + } + FINALLY { + finally_called ++; + } + ENDTRY; + + if (finally_called != 1) { + printf("02: FINALLY called %u times (not 1) on no exception\n", finally_called); + failed = TRUE; + } + + + /* check that finally is called on an uncaught exception */ + ex_thrown = finally_called = 0; + TRY { + TRY { + THROW(BoundsError); + } + FINALLY { + finally_called ++; + } + ENDTRY; + } + CATCH(BoundsError) { + ex_thrown++; + } + ENDTRY; + + if (finally_called != 1) { + printf("03: FINALLY called %u times (not 1) on uncaught exception\n", finally_called); + failed = TRUE; + } + + if (ex_thrown != 1) { + printf("03: %u BoundsErrors (not 1) on uncaught exception\n", ex_thrown); + failed = TRUE; + } + + + /* check that finally is called on an rethrown exception */ + ex_thrown = finally_called = 0; + TRY { + TRY { + THROW(BoundsError); + } + CATCH_ALL { + ex_thrown += 10; + RETHROW; + } + FINALLY { + finally_called += 10; + } + ENDTRY; + } + CATCH(BoundsError) { + ex_thrown ++; + } + FINALLY { + finally_called ++; + } + ENDTRY; + + if (finally_called != 11) { + printf("04: finally_called = %u (not 11) on rethrown exception\n", finally_called); + failed = TRUE; + } + + if (ex_thrown != 11) { + printf("04: %u BoundsErrors (not 11) on rethrown exception\n", ex_thrown); + failed = TRUE; + } + + + /* check that finally is called on an exception thrown from a CATCH block */ + ex_thrown = finally_called = 0; + TRY { + TRY { + THROW(BoundsError); + } + CATCH_ALL { + if(ex_thrown > 0) { + printf("05: Looping exception\n"); + failed = TRUE; + } else { + ex_thrown += 10; + THROW(BoundsError); + } + } + FINALLY { + finally_called += 10; + } + ENDTRY; + } + CATCH(BoundsError) { + ex_thrown ++; + } + FINALLY { + finally_called ++; + } + ENDTRY; + + if (finally_called != 11) { + printf("05: finally_called = %u (not 11) on exception thrown from CATCH\n", finally_called); + failed = TRUE; + } + + if (ex_thrown != 11) { + printf("05: %u BoundsErrors (not 11) on exception thrown from CATCH\n", ex_thrown); + failed = TRUE; + } + + if(failed == FALSE ) + printf("success\n"); +} + +int main(void) +{ + except_init(); + run_tests(); + except_deinit(); + exit(failed?1:0); +}
Index: epan/dissectors/packet-frame.c =================================================================== RCS file: /cvs/ethereal/epan/dissectors/packet-frame.c,v retrieving revision 1.1.1.2 retrieving revision 1.2 diff -u -r1.1.1.2 -r1.2 --- epan/dissectors/packet-frame.c 4 Jul 2005 18:17:31 -0000 1.1.1.2 +++ epan/dissectors/packet-frame.c 27 Jul 2005 22:30:16 -0000 1.2 @@ -198,6 +198,15 @@ 0, 0, cap_len, "Capture Length: %d byte%s", cap_len, plurality(cap_len, "", "s")); + /* we are going to be using proto_item_append_string() on + * hf_frame_protocols, and we must therefore disable the + * TRY_TO_FAKE_THIS_ITEM() optimisation for the tree by + * setting it as visible. + * + * See proto.h for details. + */ + PTREE_DATA(fh_tree)->visible=1; + ti = proto_tree_add_string(fh_tree, hf_frame_protocols, tvb, 0, 0, ""); PROTO_ITEM_SET_GENERATED(ti);
- Prev by Date: Re: [Ethereal-dev] Re: [Ethereal-cvs] rev 15092: /trunk/: Makefile.nmake randpkt.c
- Next by Date: [Ethereal-dev] iax2 dissector patch
- Previous by thread: Re: [Ethereal-dev] ememification of tvb_get_tring() and friends
- Next by thread: [Ethereal-dev] iax2 dissector patch
- Index(es):