This is the mail archive of the gcc@gcc.gnu.org mailing list for the GCC project.
Index Nav: | [Date Index] [Subject Index] [Author Index] [Thread Index] | |
---|---|---|
Message Nav: | [Date Prev] [Date Next] | [Thread Prev] [Thread Next] |
Other format: | [Raw text] |
Oops, seems my last email didn't come through anyway. Anyway, here's the stuff: RES is my project to test (at compile time) whether C++ code can throw unhandled exceptions. I tidied up my source, made a patch, wrote and tested some testcases, and wrote some documentation. There should be three attachments here: - res.diff - res.txt - test.tar.gz I've never made patch files before, but hopefully this one is OK. I used [diff -Naur original/gcc-3.4.2/gcc res/gcc-3.4.2/gcc > res.diff] I tested it on a fresh gcc-3.4.2 extract and it worked with [patch -p1 < res.diff] For a quick test, just call gcc with -Wres (or -Wres-debug to add the debug info). I haven't tested on much real-world code, there's a chance it may still have bugs and segfault or something. It's still pre-alpha though. I tried to use GCC coding style but I didn't see an 80 character/line limit anywhere in the document, so I didn't use any limit. Perhaps someone will bite me for this or perhaps you'll all celebrate the beginning of a new era of sensible unlimited lines :) anyway it's pre-alpha so it hardly matters at present - it's just far easier for me to read. Testcases: Unzip test.tar.gz and read test/tests.txt for instructions. All the testcases except the last work as they should.
diff -Naur original/gcc-4.3.2/gcc/c-opts.c res/gcc-4.3.2/gcc/c-opts.c --- original/gcc-4.3.2/gcc/c-opts.c 2008-01-23 03:11:44.000000000 +1300 +++ res/gcc-4.3.2/gcc/c-opts.c 2008-12-16 17:44:42.000000000 +1300 @@ -40,6 +40,8 @@ #include "mkdeps.h" #include "target.h" #include "tm_p.h" +/* <[SPH]> */ +#include "res.h" #ifndef DOLLARS_IN_IDENTIFIERS # define DOLLARS_IN_IDENTIFIERS true @@ -510,6 +512,21 @@ error ("argument %qs to %<-Wnormalized%> not recognized", arg); break; + /* <[SPH]> */ + case OPT_Wresl_: + res_dwb_add_filelist(arg); + case OPT_Wres_cnr: + case OPT_Wres_mes: + /* Fall through. */ + case OPT_Wres: + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%sWres.", res_intmsg_prefix); + res_check_exception_specification = true; + warn_restrictive_exception_specification = true; + break; + + case OPT_Wreturn_type: warn_return_type = value; break; @@ -530,6 +547,14 @@ cpp_opts->warn_trigraphs = value; break; + /* <[SPH]> */ + case OPT_Wtt: + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%sWtt.", res_intmsg_prefix); + res_check_exception_specification = true; + break; + case OPT_Wundef: cpp_opts->warn_undef = value; break; @@ -552,6 +577,18 @@ warn_write_strings = value; break; + /* <[SPH]> */ + case OPT_Wxesl_: + res_dwb_add_filelist(arg); + /* Fall through. */ + case OPT_Wxes: + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%sWxes.", res_intmsg_prefix); + res_check_exception_specification = true; + warn_excessive_exception_specification = true; + break; + case OPT_Weffc__: warn_ecpp = value; if (value) @@ -1687,3 +1724,247 @@ break; } } + + + +/* <[SPH]> */ +/* RES: Restrictive Exception Specification. */ + +bool res_check_exception_specification = FALSE; + +/* Internal warning prefix. NULL = no internal warnings. */ +const char* res_intmsg_prefix = RES_INTMSG_PREFIX; + +/* RES: Declaration Whitelisting & Blacklisting. */ + +const char* +res_system_path = RES_DWB_SYSTEM_DIR; + +/* TREE_LISTs of list members. */ +static tree +res_dwb_filelist = NULL_TREE; + +/* Parse the file-list after the command-line option -Wresl=. + Save each identified node into res_filelist. + Parameters: + arg: The null-terminated string argument directly following the command line option. */ + +void +res_dwb_add_filelist(const char* arg) +{ + tree last_node = NULL_TREE; + const char* ch = arg; + const char* start = arg; + + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%s-Wresl arg = \"%s\".", res_intmsg_prefix, arg); + + /* Parse the file-list. + Step through characters one at a time. */ + while(true) + { + if(((*ch) == RES_DWB_CHAR_SEPARATOR) || ((*ch) == '\0')) + /* New list node. */ + { + bool is_blacklist = false; + bool is_system = false; + bool is_partial = false; + const char* end = ch; + int len = end - start; + + /* <TEMP> */ + /* For disabling verbose debugging output. */ + if(len > 0) + if((*start) == '=') + res_intmsg_prefix = NULL; + + if(len > 0) + if((*start) == RES_DWB_CHAR_BLACKLIST) + /* Blacklist. [-foo.h] makes <foo.h> warnable. */ + { + is_blacklist = true; + start++; + len--; + } + + if(len > 0) + if((*start) == RES_DWB_CHAR_SYSTEM) + /* System path. [^foo.h] matches <foo.h> not "foo.h". */ + { + is_system = true; + start++; + len--; + } + + if(len > 0) + if((*(end - 1)) == RES_DWB_CHAR_PARTIAL) + /* Partial match. [foo+] matches any file starting with "foo". */ + { + is_partial = true; + end--; + len--; + } + + /* Build and add the new node to the list. */ + { + tree node; + size_t flags = 0; + + if(is_blacklist) flags |= RES_DWB_FLAG_BLACKLIST; + if(is_system) flags |= RES_DWB_FLAG_SYSTEM; + if(is_partial) flags |= RES_DWB_FLAG_PARTIAL; + + /* Build the new node. */ + node = tree_cons((tree)start, (tree)end, NULL_TREE); + TREE_TYPE(node) = (void*)flags; + + /* Attach the new node to the tail of the list. */ + if(last_node == NULL_TREE) + res_dwb_filelist = node; + else + TREE_CHAIN(last_node) = node; + + last_node = node; + } + + /* <TEMP> */ + if(res_intmsg_prefix) + { + char buffer[110]; + int buffer_len = (len > 100) ? 100 : len; + memcpy(buffer, start, buffer_len); + buffer[buffer_len] = '\0'; + /* Eg: [TEST: Added "foo" to blacklist as part user path.] */ + warning(0l, "%sAdded \"%s\" to %s as %s %s path.", res_intmsg_prefix, buffer, is_blacklist ? "blacklist" : "whitelist", is_partial ? "partial" : "full", is_system ? "system" : "user"); + } + + start = ch + 1; + + if((*ch) == '\0') + break; + } + + if((*ch) == RES_DWB_CHAR_ESCAPE) + /* Escape character. */ + { + ch++; + if((*ch) == '\0') + /* Escape without following character. */ + break; + } + + ch++; + } +} + +/* Check res_whitelist and res_blacklist to see if calls to items declared in the file should generate -Wres warnings. + Parameters: + name: The filename to be checked. + Return: + true: Generate -Wres warnings for calls to this file. + false: Do not generate -Wres warnings for calls to this file. */ + +bool +res_dwb_file_warnable(const char* name_start) +{ + bool is_system = false; + const char* name_ch = name_start; + const char* system_ch = res_system_path; + const char* name_end = name_start + strlen(name_start); + tree node; + + /* Check whether "name" is inside the system path. + Step through characters one at a time. */ + while(true) + { + if((*system_ch) == '\0') + /* "name" is inside the system path. */ + { + is_system = true; + name_start = name_ch; + break; + } + + /* If (*name_ch) == '\0', will always break here. */ + if((*name_ch) != (*system_ch)) + break; + + system_ch++; + name_ch++; + } + + /* Check every node on the list to see if it matches. */ + for(node = res_dwb_filelist; node != NULL_TREE; node = TREE_CHAIN(node)) + { + /* Extract flags. */ + size_t node_flags = (size_t)TREE_TYPE(node); + bool node_is_blacklist = (node_flags & RES_DWB_FLAG_BLACKLIST) != 0; + bool node_is_system = (node_flags & RES_DWB_FLAG_SYSTEM) != 0; + bool node_is_partial = (node_flags & RES_DWB_FLAG_PARTIAL) != 0; + const char* node_start; + const char* node_end; + const char* node_ch; + const char* name_ch; + + /* if(node_is_system ^^ is_system) */ + if((node_is_system && !is_system) || (!node_is_system && is_system)) + /* Node is a system path but query isn't, or vice-versa. */ + continue; + + node_start = (const char*)TREE_PURPOSE(node); + node_end = (const char*)TREE_VALUE(node); + + /* Step through characters one at a time. */ + node_ch = node_start; + name_ch = name_start; + while(true) + { + if((*node_ch) == RES_DWB_CHAR_ESCAPE) + /* Escape character. */ + { + node_ch++; + if(node_ch >= node_end) + /* Escape without following character. */ + break; + } + + if(node_ch >= node_end) + /* Name matched, at least partially. */ + { + if((name_ch == name_end) || node_is_partial) + { + /* <TEMP> */ + if(res_intmsg_prefix) + { + char buffer[110]; + int buffer_len = node_end - node_start; + if(buffer_len > 100) + buffer_len = 100; + memcpy(buffer, node_start, buffer_len); + buffer[buffer_len] = '\0'; + /* Eg: [TEST: "foo" matched "f\o". Blacklisted by partial user path.] */ + warning(0, "%s\"%s\" matched \"%s\". %s by %s %s path.", res_intmsg_prefix, name_start, buffer, node_is_blacklist ? "Blacklisted" : "Whitelisted", node_is_partial ? "partial" : "full", node_is_system ? "system" : "user"); + } + + return node_is_blacklist; + } + + break; + } + + if((*node_ch) != (*name_ch)) + /* Strings don't match. */ + break; + + node_ch++; + name_ch++; + } + } + + /* If no list entries match, use the defaults. */ + if(is_system) + return RES_DWB_CHECK_SYSTEM_PATH; + else + return RES_DWB_CHECK_USER_PATH; +} diff -Naur original/gcc-4.3.2/gcc/c.opt res/gcc-4.3.2/gcc/c.opt --- original/gcc-4.3.2/gcc/c.opt 2008-01-13 13:22:38.000000000 +1300 +++ res/gcc-4.3.2/gcc/c.opt 2008-12-17 01:00:41.000000000 +1300 @@ -393,6 +393,31 @@ C++ ObjC++ Var(warn_reorder) Warning Warn when the compiler reorders code +; <[SPH]> +Wres +C++ Var(warn_restrictive_exception_specification) Warning +Warn when an exception may be thrown that isn't listed in the function's exception specifier + +; <[SPH]> +Wres-bl-unk +C++ Var(warn_res_blacklist_unknown) Warning +When using -Wres or -Wxes, this forces checks on declarations not in files, such as builtins. This does not enable -Wres + +; <[SPH]> +Wres-cnr +C++ Var(warn_res_cnr) Warning +Additional warning for -Wres that warns when a catch(...) has no rethrow. Using this also enables -Wres + +; <[SPH]> +Wres-mes +C++ Var(warn_res_mes) Warning +Additional warning for -Wres that warns when a function lacks an exception specification + +; <[SPH]> +Wresl= +C++ Warning Joined +-Wresl=<args> Warn when an exception may be thrown that isn't listed in the function's exception specifier. <args> allows whitelisting calls to functions declared in listed files. See <FIXME> for usage + Wreturn-type C ObjC C++ ObjC++ Var(warn_return_type) Warning Warn whenever a function's return type defaults to \"int\" (C), or about inconsistent return types (C++) @@ -445,6 +470,11 @@ C ObjC C++ ObjC++ Warning Warn if trigraphs are encountered that might affect the meaning of the program +; <[SPH]> +Wtt +C++ Var(warn_throw_terminate) Warning +Warn when an empty throw statement is used outside of a catch block + Wundeclared-selector ObjC ObjC++ Var(warn_undeclared_selector) Warning Warn about @selector()s without previously declared methods @@ -473,6 +503,16 @@ C ObjC C++ ObjC++ Var(warn_write_strings) Warning In C++, nonzero means warn about deprecated conversion from string literals to `char *'. In C, similar warning, except that the conversion is of course not deprecated by the ISO C standard. +; <[SPH]> +Wxes +C++ Var(warn_excessive_exception_specification) Warning +Warn when a catch block or function exception specifier includes a type that will never be thrown past it. + +; <[SPH]> +Wxesl= +C++ Warning Joined +-Wxesl=<args> Warn when a catch block or function exception specifier includes a type that will never be thrown past it. <args> allows whitelisting calls to functions declared in listed files. See <FIXME> for usage. + Wpointer-sign C ObjC Var(warn_pointer_sign) Init(-1) Warning Warn when a pointer differs in signedness in an assignment diff -Naur original/gcc-4.3.2/gcc/cp/except.c res/gcc-4.3.2/gcc/cp/except.c --- original/gcc-4.3.2/gcc/cp/except.c 2007-09-06 15:33:46.000000000 +1200 +++ res/gcc-4.3.2/gcc/cp/except.c 2008-12-17 00:59:59.000000000 +1300 @@ -38,6 +38,8 @@ #include "tree-inline.h" #include "tree-iterator.h" #include "target.h" +/* <[SPH]> */ +#include "res.h" static void push_eh_cleanup (tree); static tree prepare_eh_type (tree); @@ -1015,3 +1017,957 @@ check_handlers_1 (handler, i); } } + + + +/* <[SPH]> */ +/* RES: Restrictive Exception Specification. + Code for -Wres, -Wres-wl, -Wres-bl, -Wxes, -Wtt + These functions are declared in res.h */ + +static tree +res_throwable; +/* This is the TREE_LIST to which thrown types are added. + This list is for the innermost try statement-block being parsed, or the function if the parser is not currently inside any try statement-block. */ + +static tree +res_catchable; +/* This is the list of types thrown inside the previous try block that have not yet been matched by a handler. + If the parser is not parsing handlers for a try block this is equal to void_type_node. */ + +static tree +res_caught; +/* Inside catch(type), res_caught = type. + Inside catch(...), res_caught = TREE_LIST of remaining throwable types or NULL_TREE if no types. + Outside all catch()es, res_caught = void_type_node; */ + +/* Structure of res_throwable & res_catchable. +The list nodes have these components: +- TREE_TYPE(node) = The thrown type. +- TREE_VALUE(node) = The location where the throw was called. + <FIXME>: Currently a size_t typecast to void*. +- TREE_PURPOSE(node) = The thing throwing the type: + If this is a function, this is the function declaration tree. + If this is a throw, this is NULL_TREE. <FIXME:Replace with expression?> +If anything is throwable, there is only one node, whose TREE_TYPE(node) is NULL_TREE. +If nothing is throwable, the list is empty, equal to NULL_TREE. */ + +bool +res_in_function = false; +/* Indicates whether processing is inside a function. */ + + + +/* Called at the start of a function if res_check_exception_specification is active. + Stores the current throwable and catchable lists and catch type, to be recovered when the function ends (useful for function bodies nested inside function bodies). + Initialises res_throwable. + Parameters: + throwable: Pointer to temporary storage for res_throwable. + catchable: Pointer to temporary storage for res_catchable. + caught: Pointer to temporary storage for res_caught. */ + +void +res_on_begin_function(tree* throwable, tree* catchable, tree* caught) +{ + /* Temporarily store nested variables. */ + *throwable = res_throwable; + *catchable = res_catchable; + *caught = res_caught; + + res_throwable = NULL_TREE; + res_catchable = void_type_node; + res_caught = void_type_node; + res_in_function = true; + + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%sfunction begins.", res_intmsg_prefix); +} + +/* Called at the end of a function if res_check_exception_specification is active. + Checks the exception specification against types that can be thrown. + Outputs warnings if any thrown types aren't in the specification. + Parameters: + throwable: Pointer to where res_throwable was stored by res_on_begin_function(). + catchable: Pointer to where res_catchable was stored by res_on_begin_function(). + caught: Pointer to where res_caught was stored by res_on_begin_function(). + function: The FUNCTION_DECL for the ending function. */ + +void +res_on_end_function(tree* throwable, tree* catchable, tree* caught, tree function) +{ + tree functype = TREE_TYPE(function); + tree exceptions = TYPE_RAISES_EXCEPTIONS(functype); + + /* <FIXME> Unnecessary? */ + /* Check whether res_on_begin_function() was called for this function. + Protects against checking clones. */ + /*if(!res_in_function) + return; + else + res_in_function = false;*/ + + /* <TEMP> */ + if(res_intmsg_prefix) + { + warning(0, "%sfunction %q+F ends.", res_intmsg_prefix, function); + res_warnlist(res_throwable); + } + + /* -Wres, -Wresl= */ + if(warn_restrictive_exception_specification) + res_check_res(function, exceptions); + + /* -Wxes, -Wxesl= */ + if(warn_excessive_exception_specification) + res_check_xes(function, exceptions); + + res_empty(&res_throwable); + + /* Recover previous values. */ + res_throwable = *throwable; + res_catchable = *catchable; + res_caught = *caught; +} + +/* Checks the exception specification against types that can be thrown. + Warns when thrown types aren't matched by a specification entry. + Parameters: + function: The FUNCTION_DECL for the ending function. + exceptions: The exception specifier for function. */ + +void +res_check_res(tree function, tree exceptions) +{ + tree list_node; + int unmatched = 0; + + /* If the function does not have an exception specification, it may throw any exception. */ + if(exceptions == NULL_TREE) + { + /* -Wres-mes */ + if(warn_res_mes) + warning(0, "RES: %q+F has no exception specification.", function); + + return; + } + + /* No exceptions can propogate to outside the function. */ + if(res_throwable == NULL_TREE) + return; + + if(TREE_TYPE(res_throwable) == NULL_TREE) + /* The function calls another function without an exception specification, and does not catch this. */ + { + tree thrower = TREE_PURPOSE(res_throwable); + location_t loc = (size_t)TREE_VALUE(res_throwable); + + if(thrower != NULL_TREE) + if(TREE_CODE(thrower) == FUNCTION_DECL) + { + warning(0, "RES: %q+F may terminate due to the uncaught and unspecified exceptions from calls to %qF, which may throw anything.", function, thrower); + inform("RES: %qF called here.%H", thrower, &loc); + inform("RES: %q+F declared here.", thrower); + return; + } + + /* This is unlikely to be called unless RES_CALCULATE_CATCH_ANY_RETHROW is false. */ + warning(0, "RES: %q+F may terminate due to uncaught and unspecified exceptions, which may be of any type.", function); + return; + } + + if(TREE_VALUE(exceptions) == NULL_TREE) + /* The function is defined as throw(). */ + { + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%sCan't throw anything.", res_intmsg_prefix); + + exceptions = NULL_TREE; + } + + /* Check all possibly thrown types vs the function's exception specification. */ + for(list_node = res_throwable; list_node != NULL_TREE; list_node = TREE_CHAIN(list_node)) + { + tree type; + tree thrower; + location_t loc; + tree spec_node; + + type = TREE_TYPE(list_node); + thrower = TREE_PURPOSE(list_node); + loc = (location_t)(size_t)TREE_VALUE(list_node); + + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%s%qT.%H", res_intmsg_prefix, type, &loc); + + /* Step through all types in the exception specification, exiting if a match is found */ + for(spec_node = exceptions; spec_node != NULL_TREE; spec_node = TREE_CHAIN(spec_node)) + { + tree spec_type = TREE_VALUE(spec_node); + if(same_or_base_type_p(spec_type, type)) + { + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%smatched by %qT.", res_intmsg_prefix, spec_type); + break; + } + } + + if(spec_node == NULL_TREE) + /* Thrown type isn't part of the exception specification. */ + { + unmatched++; + + if(unmatched == 1) + warning(0, "RES: %q+F may terminate due to the following uncaught and unspecified exceptions:", function); + + if(unmatched <= RES_MAX_RES_EXCEPTIONS_LISTED) + { + if(thrower != NULL_TREE) + switch(TREE_CODE(thrower)) + { + case FUNCTION_DECL: + { + const char* filename = DECL_SOURCE_FILE(thrower); + int line = DECL_SOURCE_LINE(thrower); + inform("RES: %qT from %qF (declaration at %s:%d).%H", type, thrower, filename, line, &loc); + } + break; + default: + inform("RES: %qT from here.%H", type, &loc); + } + else + inform("RES: %qT from here.%H", type, &loc); + } + } + } + + if(unmatched > RES_MAX_RES_EXCEPTIONS_LISTED) + { + int unlisted; + unlisted = unmatched - RES_MAX_RES_EXCEPTIONS_LISTED; + inform("RES: %d further unlisted type%s may be thrown.", unlisted, (unlisted == 1) ? "" : "s"); + } +} + +/* Checks the exception specification against types that can be thrown. + Warns when specification entries aren't matched by a thrown type. + Parameters: + function: The FUNCTION_DECL for the ending function. + exceptions: The exception specifier for function. */ + +void +res_check_xes(tree function, tree exceptions) +{ + int unmatched = 0; + tree spec_node; + + if(exceptions == NULL_TREE) + /* If the function does not have an exception specifier, it may throw any exception. */ + { + if(res_throwable == NULL_TREE) + warning(0, "XES: %q+F has no exception specification yet no exceptions can propagate past it.", function); + + return; + } + + if(res_throwable == NULL_TREE) + /* No exceptions can propogate to outside the function. */ + { + if(TREE_VALUE(exceptions) != NULL_TREE) + warning(0, "XES: %q+F has an exception specification, yet no exceptions can propagate past it.", function); + + return; + } + + if(TREE_TYPE(res_throwable) == NULL_TREE) + /* Anything is throwable. */ + return; + + if(TREE_VALUE(exceptions) == NULL_TREE) + return; + + for(spec_node = exceptions; spec_node != NULL_TREE; spec_node = TREE_CHAIN(spec_node)) + { + tree spec_type = TREE_VALUE(spec_node); + + tree list_node; + for(list_node = res_throwable; list_node != NULL_TREE; list_node = TREE_CHAIN(list_node)) + { + tree list_type = TREE_TYPE(list_node); + + if(same_or_base_type_p(spec_type, list_type)) + /* Type may be caught. Don't warn. */ + break; + + if(same_or_base_type_p(list_type, spec_type)) + /* Type may be caught here too, since more-derived types are removed from lists. */ + break; + } + + if(list_node == NULL_TREE) + { + unmatched++; + + if(unmatched == 1) + warning(0, "XES: %q+F exception specification includes the following types that are unable to propagate past it:", function); + + if(unmatched <= RES_MAX_XES_EXCEPTIONS_LISTED) + inform("XES: %qT.", spec_type); + } + } + + if(unmatched > RES_MAX_XES_EXCEPTIONS_LISTED) + { + int unlisted; + unlisted = unmatched - RES_MAX_XES_EXCEPTIONS_LISTED; + inform("XES: There are %d further unlisted type%s on %q+Fs exception specification that can't propagate past it.", unlisted, (unlisted == 1) ? "" : "s", function); + } +} + +/* Called at the start of a try-catch segment if res_check_exception_specification is active. + Stores the current throwable and catchable lists and catch type, to be recovered when the try-catch block ends. + Parameters: + throwable: Pointer to temporary storage for res_throwable. + catchable: Pointer to temporary storage for res_catchable. + caught: Pointer to temporary storage for res_caught. */ + +void +res_on_begin_try(tree* throwable, tree* catchable, tree* caught) +{ + /* Temporarily store nested variables. */ + *throwable = res_throwable; + *catchable = res_catchable; + *caught = res_caught; + + res_throwable = NULL_TREE; + res_catchable = void_type_node; + res_caught = void_type_node; + + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%stry block begins.", res_intmsg_prefix); +} + +/* Called at the start of a try block if res_check_exception_specification is active. + Recovers the throwable list stored on entering the try-block. + Parameters: + throwable: Pointer to where res_throwable was stored by res_on_begin_try(). */ + +void +res_on_end_try(tree* throwable) +{ + res_catchable = res_throwable; + + /* Recover nested variable. */ + res_throwable = *throwable; + + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%stry block ends.", res_intmsg_prefix); +} + +/* Called at the end of a try-catch segment if res_check_exception_specification is active. + Merges the outer throwable list with what can be thrown out of the try-catch block. + Recovers the catchable list and catch type stored on entering the segment. + Parameters: + catchable: Pointer to where res_catchable was stored by res_on_begin_try(). + caught: Pointer to where res_caught was stored by res_on_begin_try(). */ + +void +res_on_end_handlers(tree* catchable, tree* caught) +{ + if(res_catchable == void_type_node) + res_catchable = NULL_TREE; + + res_merge(&res_throwable, &res_catchable); + + /* Recover nested variables. */ + res_catchable = *catchable; + res_caught = *caught; + + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%stry-catch segment ends.", res_intmsg_prefix); +} + +/* Called at the start of a catch block if res_check_exception_specification is active. + Sets res_caught to indicate the type(s) caught. + If it's a single type, removes the type from the catchable list. + If it's catch(...), it can catch any type that is still in the catchable list. + Also checks for -Wxes. + Parameters: + caught: tcc_type caught, or NULL_TREE for catch(...). */ + +void +res_on_begin_catch(tree caught) +{ + if(res_catchable == void_type_node) + /* This shouldn't happen. */ + res_catchable = NULL_TREE; + + if(caught == NULL_TREE) + /* catch(...) */ + { + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%scatch(...) block begins. ", res_intmsg_prefix); + + if((res_catchable == NULL_TREE) && warn_excessive_exception_specification) + warning(0, "XES: catch(...) will never catch any types."); + + res_caught = res_catchable; + + /* Indication for -Wres-cnr */ + res_catchable = void_type_node; + } + else + /* catch(type) */ + { + bool matched; + + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%scatch(%qT) block begins.", res_intmsg_prefix, caught); + + res_caught = caught; + /* Attempt to remove "caught" from the "res_catchable" list. */ + matched = res_remove(&res_catchable, caught); + + if((!matched) && warn_excessive_exception_specification) + warning(0, "XES: catch(%qT) catches a type never thrown to it.", caught); + } +} + +/* Called at the end of a catch block if res_check_exception_specification is active. + If ending a catch(...) block, cleans up the TREE_LIST in res_caught. */ + +void +res_on_end_catch(void) +{ + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%scatch block ends.", res_intmsg_prefix); + + /* -Wres-cnr */ + if(warn_res_cnr && (res_catchable == void_type_node)) + warning(0, "RES: catch(...) has no re-throw."); + + /* Check if res_caught is a TREE_LIST, and if so, empty it. */ + if(res_caught != NULL_TREE) + if(TREE_CODE(res_caught) == TREE_LIST) + res_empty(&res_caught); +} + +/* Called on a throw statement if res_check_exception_specification is active. + If a type is thrown, adds the thrown type to res_throwable. + "type" can be NULL_TREE, indicating a rethrow or throw-terminate. + Parameters: + expression: The throw expression. */ + +void +res_on_throw(tree expression) +{ + tree type; + location_t loc; + + if(expression == NULL_TREE) + type = NULL_TREE; + else + type = lvalue_type(expression); + + /* <FIXME:Doesn't work.> */ + /* loc = EXPR_LOCATION(expression); */ + loc = input_location; + + if(type == NULL_TREE) + /* throw; (no type) */ + res_on_rethrow(NULL_TREE, loc); + else + /* throw type; */ + { + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%sType %qT thrown here.", res_intmsg_prefix, type); + + /* Add "type" to res_throwable. */ + res_add_type(&res_throwable, type, NULL_TREE, loc); + } +} + +/* Called on a throw; statement (with no type) if res_check_exception_specification is active. + If outside catch block, or innermost exception block is try {}, this is a throw-terminate. -Wtt will warn here. + If innermost exception block is catch(...) block, either adds possibly thrown types to res_throwable, or sets res_throwable to ... (depending on RES_CALCULATE_CATCH_ANY_RETHROW). + If innermost exception block is catch(type) block, adds the thrown type to res_throwable. + Parameters: + thrower: The object rethrowing. <FIXME:Currently NULL_TREE>. + loc: The location of the rethrow. */ + +void +res_on_rethrow(tree thrower, size_t loc) +{ + if(res_caught == void_type_node) + { + /* -Wtt */ + if(warn_throw_terminate) + warning(0, "TT: Throw-terminate here."); + } + else + /* Innermost exception block is catch. */ + { + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%sRe-throw here.", res_intmsg_prefix); + + if(res_caught != NULL_TREE) + if(TREE_CODE(res_caught) != TREE_LIST) + /* e.g. catch(type) { throw; } */ + { + res_add_type(&res_throwable, res_caught, thrower, loc); + return; + } + + /* Indication for -Wres-cnr */ + res_catchable = NULL_TREE; + + /* e.g. catch(...) { throw; } */ + if(RES_CALCULATE_CATCH_ANY_RETHROW) + /* Add all types possibly caught by catch(...) to res_throwable. */ + res_merge(&res_throwable, &res_caught); + else + /* Add ... to res_throwable. Empty res_caught. */ + { + res_empty(&res_caught); + res_add_any(&res_throwable, thrower, loc); + } + } +} + +/* Called on a standard function call if res_check_exception_specification is active. + Checks blacklists & whitelists from -Wres-wl and -Wres-bl, to see whether to ignore this call. + Adds any types thrown by the function to res_throwable. + If the function has no exception specification, adds ... . + Parameters: + function: The FUNCTION_DECL of the called function. */ + +void +res_on_call_function(tree function) +{ + tree functype; + tree exceptions; + bool warn; + size_t loc = (size_t)input_location; + const char* file; + + if(function == NULL_TREE) + { + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%sunknown function called.", res_intmsg_prefix); + + return; + } + + /* Retrieve function name string. */ + file = DECL_SOURCE_FILE(function); + + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%sfunction %qF called (declared in %s).", res_intmsg_prefix, function, file ? file : "unknown file"); + + if(file) + /* Check whitelist & blacklist. */ + warn = res_dwb_file_warnable(file); + else + /* -Wres-bl-unk. */ + warn = warn_res_blacklist_unknown; + + if(!warn) + { + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%sMuting whitelisted %qF.", res_intmsg_prefix, function); + + return; + } + + functype = TREE_TYPE(function); + exceptions = TYPE_RAISES_EXCEPTIONS(functype); + if(exceptions) + /* Call to function with exception specification. */ + { + tree list_node; + list_node = exceptions; + if(TREE_VALUE(list_node) == NULL_TREE) + /* Call to function with empty exception specification. */ + { + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%sthrows nothing.", res_intmsg_prefix); + } + else + { + /* Step through all thrown types. */ + while(list_node != NULL_TREE) + { + tree type; + type = TREE_VALUE(list_node); + + if(type == NULL_TREE) + { + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%scan throw unknown.", res_intmsg_prefix); + } + else + { + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%scan throw %qT.", res_intmsg_prefix, type); + + /* Add "type" to res_throwable. */ + res_add_type(&res_throwable, type, function, loc); + } + + list_node = TREE_CHAIN(list_node); + } + } + } + else + /* Call to function without exception specification. */ + { + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%s%qF can throw anything.", res_intmsg_prefix, function); + + /* Add ... to res_throwable. */ + res_add_any(&res_throwable, function, loc); + } +} + + +/* Called on a function-pointer function call. + FIXME: gcc doesn't store function-pointer exception specifications yet, although it is required to comply with the C++ spec (15.4 -3- : except.spec). + Parameters: + pointer: The FUNCTION_DECL of the function pointer. */ + +void +res_on_call_function_pointer(tree pointer) +{ + /* <TEMP> */ + if(res_intmsg_prefix) + warning(0, "%sfunction pointer called. %qD. <FIXME>", res_intmsg_prefix, pointer); +} + + +/* Adds a type to a type list. + Ensures that no types exist that are derived from other types in the list, as catch(type) catches types derived from "type". + Parameters: + list: Pointer to the list to which the type is to be added. + type: Type to be added. + thrower: Object throwing the type, such as a FUNCTION_DECL. + loc: Where the throwing occurred. */ + +void +res_add_type(tree* list, tree type, tree thrower, size_t loc) +{ + tree last_node; + tree new_tree; + + if((*list) != NULL_TREE) + if(TREE_TYPE(*list) == NULL_TREE) + /* list is ... */ + return; + + if(res_prep_add_type(list, type, &last_node)) + { + /* Build the new node. */ + new_tree = res_make_node(type, thrower, loc); + + /* Attach the new node. */ + if(last_node == NULL_TREE) + *list = new_tree; + else + TREE_CHAIN(last_node) = new_tree; + } +} + +/* Sets a *list to ... . + ... is indicated by a 1-node tree whose TREE_TYPE() is NULL_TREE. + Parameters: + list: Pointer to the list that is becoming ... . + thrower: Object throwing the type, such as a FUNCTION_DECL. + loc: Where the throwing occurred. */ + +void +res_add_any(tree* list, tree thrower, size_t loc) +{ + if(*list == NULL_TREE) + *list = res_make_node(NULL_TREE, thrower, loc); + else + { + if(TREE_TYPE(*list) != NULL_TREE) + { + res_empty(list); + *list = res_make_node(NULL_TREE, thrower, loc); + } + /* else tree is already pointing at a function which may throw anything. */ + } +} + +/* Merges the contents of two type lists, and sets *from to NULL_TREE. + If *into is ..., *from is simply emptied. + If *from is ..., *into becomes *from. + Ensures that no types exist that are derived from other types in the list. + Parameters: + into: Pointer to the list to add to. + from: Pointer to the list to move from. Equal to NULL_TREE on exit. */ + +void +res_merge(tree* into, tree* from) +{ + tree list_node; + + if((*from) == NULL_TREE) + /* from is empty. */ + return; + + if((*into) == NULL_TREE) + /* into is empty. */ + { + *into = *from; + *from = NULL_TREE; + return; + } + + if(TREE_TYPE(*into) == NULL_TREE) + /* into = ... */ + { + res_empty(from); + return; + } + + if(TREE_TYPE(*from) == NULL_TREE) + /* from = ... */ + { + res_empty(into); + *into = *from; + *from = NULL_TREE; + return; + } + + list_node = *from; + /* Step through all types from *from, adding each to *into. */ + while(list_node) + { + tree next = TREE_CHAIN(list_node); + tree list_type = TREE_TYPE(list_node); + tree last_node; + + if(res_prep_add_type(into, list_type, &last_node)) + { + /* Reuse list_node. */ + TREE_CHAIN(last_node) = list_node; + TREE_CHAIN(list_node) = NULL_TREE; + } + else + /* <FIXME>: else delete list_node. */ + {} + + list_node = next; + } + + *from = NULL_TREE; +} + +/* Internal function for res_add_type() and res_merge(). + Removes all types derived from "type". + Exits if "type" or a less-derived type of "type" is found. + Parameters: + list: Pointer to the list where the type is to be added. + type: The type to be added. + last: Return-by-reference. Recieves address of the last node in the list. Allows new node to be added to the back. + Return: + Indicates whether type needs to be added. + false means "type" or a less-derived type of "type" has been found. */ + +bool +res_prep_add_type(tree* list, tree type, tree* last) +{ + tree list_node; + tree prev_node; + + /* Step through list, removing types derived from "type". + Exit if "type" or a less-derived base-class of "type" is found. */ + list_node = *list; + prev_node = NULL_TREE; + while(list_node != NULL_TREE) + { + tree list_type = TREE_TYPE(list_node); + + /* Check whether "list_type" is "type", or a less-derived base-class of "type". If so, "type" doesn't need to be added. */ + if(same_or_base_type_p(list_type, type)) + return false; + + /* Check whether "type" is a less-derived base-class of "list_type". If so, remove "list_node". */ + if(same_or_base_type_p(type, list_type)) + { + tree next_node = TREE_CHAIN(list_node); + /* <FIXME>: delete list_node */ + + if(prev_node == NULL_TREE) + *list = next_node; + else + TREE_CHAIN(prev_node) = next_node; + + list_node = next_node; + } + else + { + prev_node = list_node; + list_node = TREE_CHAIN(list_node); + } + } + + *last = prev_node; + + /* "type" needs to be added. */ + return true; +} + +/* Cleans up a TREE_LIST, deleting all chained nodes. + Parameters: + list: Pointer to the list to be emptied. */ + +void +res_empty(tree* list) +{ + tree list_node; + list_node = *list; + *list = NULL_TREE; + + /* Steps through all types in *list, removing them. */ + while(list_node != NULL_TREE) + { + tree next = TREE_CHAIN(list_node); + /* <FIXME>: delete list_node */ + list_node = next; + } +} + +/* Attempts to remove "type" or any types derived from "type" from *list. + Parameters: + list: Pointer to the list to which "type" or its derivatives are to be removed. + type: tcc_type to remove. + Return: + Indicates whether type is matched. */ + +bool +res_remove(tree* list, tree type) +{ + tree list_node = *list; + tree prev_node = NULL_TREE; + bool matched = false; + + if((*list) != NULL_TREE) + if(TREE_TYPE(*list) == NULL_TREE) + /* List is ... */ + return true; + + /* Step through all listed types. + If a more derived type of "type" is found, remove it. + If type or a less derived type of "type" is found, exit. */ + while(list_node != NULL_TREE) + { + tree next = TREE_CHAIN(list_node); + tree list_type = TREE_TYPE(list_node); + + /* Check whether "type" is "list_type", or a less-derived base-class of "list_type". If so, "list_node" is removed. */ + if(same_or_base_type_p(type, list_type)) + { + matched = true; + + if(prev_node) + TREE_CHAIN(prev_node) = next; + else + *list = next; + + /* <FIXME>: delete list_node */ + list_node = next; + } + else + { + /* Check whether "list_type" is a less-derived base-class of "type". If so, nothing can be done. */ + if(same_or_base_type_p(list_type, type)) + return true; + + prev_node = list_node; + } + + list_node = next; + } + + return matched; +} + +/* Makes a TREE_LIST node, whose TREE_CHAIN() is set to NULL_TREE. + Parameters: + type: tcc_type. stored in TREE_TYPE(). + thrower: stored in TREE_PURPOSE(). + loc: location where type is thrown. Stored via typecast in TREE_VALUE(). <FIXME:This OK?> + Return: + The newly built TREE_LIST node. */ + +tree +res_make_node(tree type, tree thrower, size_t loc) +{ + tree new_tree = tree_cons(thrower, (tree)(void*)loc, NULL_TREE); + TREE_TYPE(new_tree) = type; + + return new_tree; +} + +/* Dumps the types in list for debugging, as warnings. + One line per type. + Parameters: + list: The list to output. */ + +void +res_warnlist(tree list) +{ + if(res_intmsg_prefix == NULL) + return; + + if(list == NULL_TREE) + warning(0, "%sTypelist: No types.", res_intmsg_prefix); + else + { + tree list_node; + for(list_node = list; list_node != NULL_TREE; list_node = TREE_CHAIN(list_node)) + { + tree type; + tree thrower; + location_t loc; + type = TREE_TYPE(list_node); + thrower = TREE_PURPOSE(list_node); + loc = (location_t)(size_t)TREE_VALUE(list_node); + + if(type == NULL_TREE) + { + if(thrower == NULL_TREE) + warning(0, "%sTypelist: Any type.%H", res_intmsg_prefix, &loc); + else + warning(0, "%sTypelist: Any type. Thrown by %qD.%H", res_intmsg_prefix, thrower, &loc); + } + else + { + if(thrower == NULL_TREE) + warning(0, "%sTypelist: %qT.%H", res_intmsg_prefix, type, &loc); + else + warning(0, "%sTypelist: %qT. Thrown by %qD.%H", res_intmsg_prefix, type, thrower, &loc); + } + } + } +} diff -Naur original/gcc-4.3.2/gcc/cp/parser.c res/gcc-4.3.2/gcc/cp/parser.c --- original/gcc-4.3.2/gcc/cp/parser.c 2008-08-08 00:27:48.000000000 +1200 +++ res/gcc-4.3.2/gcc/cp/parser.c 2008-12-15 19:56:06.000000000 +1300 @@ -37,6 +37,8 @@ #include "target.h" #include "cgraph.h" #include "c-common.h" +/* <[SPH]> */ +#include "res.h" /* The lexer. */ @@ -13782,6 +13784,7 @@ ctor_initializer_p = cp_parser_ctor_initializer_opt (parser); /* Parse the function-body. */ cp_parser_function_body (parser); + /* Finish the function body. */ finish_function_body (body); @@ -15586,14 +15589,34 @@ cp_parser_try_block (cp_parser* parser) { tree try_block; + /* <[SPH]> */ + tree throwable; + tree catchable; + tree caught; + + /* <[SPH]> */ + if (res_check_exception_specification) + /* Processing for -Wres & -Wxes. */ + res_on_begin_try (&throwable, &catchable, &caught); cp_parser_require_keyword (parser, RID_TRY, "`try'"); try_block = begin_try_block (); cp_parser_compound_statement (parser, NULL, true); finish_try_block (try_block); + + /* <[SPH]> */ + if (res_check_exception_specification) + /* Processing for -Wres & -Wxes. */ + res_on_end_try (&throwable); + cp_parser_handler_seq (parser); finish_handler_sequence (try_block); + /* <[SPH]> */ + if (res_check_exception_specification) + /* Processing for -Wres & -Wxes. */ + res_on_end_handlers (&catchable, &caught); + return try_block; } @@ -15666,8 +15689,19 @@ declaration = cp_parser_exception_declaration (parser); finish_handler_parms (declaration, handler); cp_parser_require (parser, CPP_CLOSE_PAREN, "`)'"); + + /* <[SPH]> */ + if (res_check_exception_specification) + /* Processing for -Wres & -Wxes. */ + res_on_begin_catch (HANDLER_TYPE(handler)); + cp_parser_compound_statement (parser, NULL, false); finish_handler (handler); + + /* <[SPH]> */ + if (res_check_exception_specification) + /* Processing for -Wres & -Wxes. */ + res_on_end_catch (); } /* Parse an exception-declaration. @@ -15750,6 +15784,11 @@ expression = cp_parser_assignment_expression (parser, /*cast_p=*/false); + /* <[SPH]> */ + if (res_check_exception_specification) + /* Processing for -Wres & -Wxes. */ + res_on_throw (expression); + return build_throw (expression); } @@ -16766,6 +16805,16 @@ bool saved_in_function_body; unsigned saved_num_template_parameter_lists; + /* <[SPH]> */ + tree throwable; + tree catchable; + tree caught; + + /* <[SPH]> */ + if (res_check_exception_specification) + /* Processing for -Wres & -Wxes. */ + res_on_begin_function (&throwable, &catchable, &caught); + saved_in_function_body = parser->in_function_body; parser->in_function_body = true; /* If the next token is `return', then the code may be trying to @@ -16823,6 +16872,16 @@ = saved_num_template_parameter_lists; parser->in_function_body = saved_in_function_body; + /* <[SPH]> */ + if (res_check_exception_specification) + { + /* <FIX>:Is there a better way? */ + tree temp_function_decl = current_function_decl; + current_function_decl = fn; + res_on_end_function (&throwable, &catchable, &caught, fn); + current_function_decl = temp_function_decl; + } + return fn; } @@ -19366,6 +19425,7 @@ } } + /* OpenMP 2.5: variable-list: identifier @@ -20455,6 +20515,12 @@ SET_EXPR_LOCATION (stmt, pragma_tok->location); } + + + + + + /* The parser. */ static GTY (()) cp_parser *the_parser; diff -Naur original/gcc-4.3.2/gcc/cp/semantics.c res/gcc-4.3.2/gcc/cp/semantics.c --- original/gcc-4.3.2/gcc/cp/semantics.c 2008-06-11 18:47:36.000000000 +1200 +++ res/gcc-4.3.2/gcc/cp/semantics.c 2008-12-14 19:56:28.000000000 +1300 @@ -46,6 +46,8 @@ #include "tree-iterator.h" #include "vec.h" #include "target.h" +/* <[SPH]> */ +#include "res.h" /* There routines provide a modular interface to perform many parsing operations. They may therefore be used during actual parsing, or @@ -1974,6 +1976,12 @@ result = build_call_list (TREE_TYPE (result), orig_fn, orig_args); KOENIG_LOOKUP_P (result) = koenig_p; } + + /* <[SPH]> */ + if (res_check_exception_specification && (fn != NULL_TREE)) + /* Processing for -Wres & -Wxes. */ + res_on_call_function (fn); + return result; } diff -Naur original/gcc-4.3.2/gcc/res.h res/gcc-4.3.2/gcc/res.h --- original/gcc-4.3.2/gcc/res.h 1970-01-01 12:00:00.000000000 +1200 +++ res/gcc-4.3.2/gcc/res.h 2008-12-16 17:45:04.000000000 +1300 @@ -0,0 +1,111 @@ +/* C++ Restrictive Exception Specification (RES) warning mechanic. + Copyright (C) 2008 Free Software Foundation, Inc. + Free Software Foundation, Inc. + Contributed by Simon Hill. (yacwroy@gmail.com) + +This file is part of GCC. + +GCC 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 3, or (at your option) +any later version. + +GCC 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 GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + + +/* <[SPH]> */ + +/* Internal warning prefix. NULL = no internal warnings. */ +#define RES_INTMSG_PREFIX "------------------------------------: " +/* #define RES_INTMSG_PREFIX NULL */ +const char* +res_intmsg_prefix; + + + +/* Restrictive Exception Specifiers. */ + +bool +res_check_exception_specification; + +/* Functions called from gcc standard. */ +void +res_on_begin_function(tree*, tree*, tree*); +void +res_on_end_function(tree*, tree*, tree*, tree); +void +res_on_begin_try(tree*, tree*, tree*); +void +res_on_end_try(tree*); +void +res_on_end_handlers(tree*, tree*); +void +res_on_begin_catch(tree); +void +res_on_end_catch(void); +void +res_on_throw(tree); +void +res_on_rethrow(tree, size_t); +void +res_on_call_function(tree); +void +res_on_call_function_pointer(tree); + +/* RES and XES checks. */ +void +res_check_res(tree function, tree exceptions); +void +res_check_xes(tree function, tree exceptions); + +/* Internal */ +void +res_add_type(tree*, tree, tree, size_t); +void +res_add_any(tree*, tree, size_t); +bool +res_prep_add_type(tree*, tree, tree*); +void +res_merge(tree*, tree*); +void +res_empty(tree*); +bool +res_remove(tree*, tree); +tree +res_make_node(tree, tree, size_t); +void +res_warnlist(tree); + +#define RES_MAX_RES_EXCEPTIONS_LISTED 8 +#define RES_MAX_XES_EXCEPTIONS_LISTED 8 +#define RES_CALCULATE_CATCH_ANY_RETHROW true + + + +/* Blacklists & Whitelists */ +void +res_dwb_add_filelist(const char*); +bool +res_dwb_file_warnable(const char*); + +#define RES_DWB_SYSTEM_DIR "/usr/include/" +#define RES_DWB_CHECK_USER_PATH true +#define RES_DWB_CHECK_SYSTEM_PATH false +#define RES_DWB_CHECK_UNKNOWN_PATH false + +#define RES_DWB_FLAG_BLACKLIST (1 << 0) +#define RES_DWB_FLAG_SYSTEM (1 << 1) +#define RES_DWB_FLAG_PARTIAL (1 << 2) + +#define RES_DWB_CHAR_SEPARATOR ',' +#define RES_DWB_CHAR_BLACKLIST '-' +#define RES_DWB_CHAR_SYSTEM '^' +#define RES_DWB_CHAR_PARTIAL '+' +#define RES_DWB_CHAR_ESCAPE '%'
Attachment:
res.txt
Description: Text document
Attachment:
test.tgz
Description: GNU Zip compressed data
Index Nav: | [Date Index] [Subject Index] [Author Index] [Thread Index] | |
---|---|---|
Message Nav: | [Date Prev] [Date Next] | [Thread Prev] [Thread Next] |