From: Mark Wooding Date: Thu, 27 Apr 2023 13:50:40 +0000 (+0100) Subject: Initial sketch. X-Git-Url: https://git.distorted.org.uk/~mdw/finally/commitdiff_plain/d58b8198f8f6153090e4adcbab2ecc5370f758cd Initial sketch. --- d58b8198f8f6153090e4adcbab2ecc5370f758cd diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e92c26 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +## Pervasive build machinery. +Makefile.in + +## Top-level generated files. +/aclocal.m4 +/autom4te.cache/ +/configure +/precomp/ + +## GNU build system machinery installed by `autoreconf'. +/config/compile +/config/config.guess +/config/config.sub +/config/depcomp +/config/install-sh +/config/ltmain.sh +/config/missing +/config/tap-driver.sh +/config/test-driver diff --git a/.skelrc b/.skelrc new file mode 100644 index 0000000..2d46352 --- /dev/null +++ b/.skelrc @@ -0,0 +1,9 @@ +;;; -*-emacs-lisp-*- + +(setq skel-alist + (append + '((author . "Mark Wooding") + (full-title . "the `Finally' package") + (library . "Finally") + (licence-text . "[[lgpl-2]]")) + skel-alist)) diff --git a/FINALLY.3 b/FINALLY.3 new file mode 100644 index 0000000..a7de5fc --- /dev/null +++ b/FINALLY.3 @@ -0,0 +1,162 @@ +.\" -*-nroff-*- +.TH control 3 "28 April 2023" "Straylight/Edgeware" "FINALLY macro package" +.SH NAME +FINALLY \- defer execution until scope exit +.SH SYNOPSIS +.nf +.B "#include " + +.BI FINALLY( stmt ); +.BI FINALLY_TAGGED( tag ", " stmt ); +.fi +. +.SH DESCRIPTION +. +.SS "General usage of FINALLY" +The +.B FINALLY +macro arranges that the statement +.I stmt +should be executed when control leaves the immediately enclosing scope, +whether this is by just running off the end, or by explicit control +flow, e.g., +.BR return , +.BR goto , +.BR break, +or +.BR continue . +If a scope contains multiple +.B FINALLY +calls, then the statements are executed in reverse order. +.PP +In particular, if the macro call is placed at top-level within a +function body, then +.I stmt +is executed when the function returns, either as a result of a +.B return +statement, or by running off the end of the function body. +.PP +With GNU-like compilers, you can arrange for the statement to be +executed when control escapes the scope as the result of a C++ exception +by setting the +.B \-fexceptions +compiler flag. +.PP +The statement will +.I not +be executed if control leaves the scope as a result of +.BR longjmp (3) +or similar. It also won't be executed if control `leaves' as a result +of a call to +.BR exit (3) +or similar; but that's almost certainly what you want. +.PP +The +.B FINALLY +macro's expansion is syntactically one or more declarations. In C89 +code, therefore, it must appear at the head of a block, before any +statements. In C99 or later, it may appear anywhere in a block. +However, because it may expand to more than one declaration, it is not +suitable for use in a +.B for +statement. +. +.SS "Macros and FINALLY_TAGGED" +At most one use of +.B FINALLY +can occur on any given line of source code. In particular, this +rule forbids macros which expand into more than one use of +.BR FINALLY , +since the macro expansions will cause them all to be credited to the +line holding the original expansion site. To avoid trouble, macro +authors should use +.B FINALLY_TAGGED +instead. Each use of +.B FINALLY_TAGGED +on the same source line must have a distinct +.I tag +argument, which may be any identifier. +. +.SS "Nonlocal transfers, and thread and process exits" +The statement will +.I not +be executed if control leaves the scope as a result of +.BR longjmp (3) +or similar. It also won't be executed if control `leaves' as a result +of a call to +.BR exit (3), +.BR pthread_exit (3), +.BR thrd_exit (3), +or similar. +.PP +You didn't want +.B FINALLY +blocks to run at +.BR exit (3) +time. Almost all uses of +.B FINALLY +are to release process-local resources such as memory, file descriptors +or locks, or something else similar. The kernel will release these by +itself when the process exits, because leaking resources when processes +crash would just be too awful to consider. All you'd achieve by +carefully freeing memory prior to +.BR exit (3) +would be to waste time. +. +.SS "Setup and configuration" +The +.B FINALLY +macro uses compiler-specific techniques. Not only does it need +different implementations on different compilers, but it may require +unusual compiler options and runtime support libraries to work. +.PP +Firstly, the +.B +header can work portably under C++11 or later, using RAII and lambdas: +idiomatic C++ doesn't benefit much from this, but the facility is there +anyway. If the header detects that such a compiler is in use (the macro +.B __cplusplus +is defined to a value greater than or equal to 201103), then it will use +the C++-specific implementation. +.PP +Otherwise, the header checks the value of +.BR FINALLY_CONFIG_FLAVOUR : +it should be one of the following options. +.TP +.B NIL +No support. An error is reported (using +.BR #error ). +.TP +.B GCC_NESTED_FUNCTIONS +Use the GNU C Compiler's support for nested functions and the +.B cleanup +function attribute. Note that this use of nested functions does not +involve the use of trampolines, and does not require an executable +stack. (This is verified as part of the package tests.) +.PP +If +.B FINALLY_CONFIG_FLAVOUR +is not set, then +.B +guesses one of these flavours based on the compiler version and other +predefined macros. This guess may be wrong, and things may not work +anyway because necessary compiler options or libraries aren't available. +.PP +The best way to set everything up correctly is to use the provided +.B FINALLY_CHECK +Autoconf macro. It will define +.B FINALLY_CONFIG_FLAVOUR +to a suitable value, and also report necessary compiler flags and +libraries in the +.B FINALLY_CFLAGS +and +.B FINALLY_LIBS +makefile variables. See the +.B finally.m4 +file's internal documentation for the details. +. +.SH BUGS +The selection of supported compilers is extremely limited. +. +.SH AUTHOR +Mark Wooding, diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..d46043e --- /dev/null +++ b/Makefile.am @@ -0,0 +1,78 @@ +### -*-automake-*- +### +### Build script for `finally' +### +### (c) 2023 Mark Wooding +### + +###----- Licensing notice --------------------------------------------------- +### +### This file is part of the `Finally' package. +### +### Finally is free software: you can redistribute it and/or modify it +### under the terms of the GNU Library General Public License as published +### by the Free Software Foundation; either version 2 of the License, or +### (at your option) any later version. +### +### Finally 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 Library General Public +### License for more details. +### +### You should have received a copy of the GNU Library General Public +### License along with Finally. If not, write to the Free Software +### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +### USA. + +include_HEADERS = +dist_man_MANS = +EXTRA_DIST = + +###-------------------------------------------------------------------------- +### The main build. + +include_HEADERS += finally.h +dist_man_MANS += FINALLY.3 + +###-------------------------------------------------------------------------- +### Test configuration. + +check_PROGRAMS = +check_LIBRARIES = +TESTS = + +AM_TESTS_ENVIRONMENT = env TEST_OUTFORM=tap +LOG_DRIVER = env AM_TAP_AWK=$(AWK) $(SHELL) \ + $(top_srcdir)/config/tap-driver.sh + +check_LIBRARIES += libfintest.a +libfintest_a_SOURCES = +libfintest_a_SOURCES += finally-test.h +libfintest_a_SOURCES += test-guts.c +if FEXCEPTIONS +libfintest_a_SOURCES += try-catch.cc +endif + +if C +## The C test program. +check_PROGRAMS += finally-test +finally_test_SOURCES = finally-test.c +if FEXCEPTIONS +nodist_EXTRA_finally_test_SOURCES = bodge.cc +endif +finally_test_LDADD = libfintest.a $(FINALLY_LIBS) +TESTS += finally-test +TESTS += examine-binary +EXTRA_DIST += examine-binary +endif + +if CXX14 +## The C++ test program. Which is actually exactly the same program, only +## (partially) compiled with a C++ compiler. +check_PROGRAMS += finally-cxx-test +finally_cxx_test_SOURCES = finally-cxx-test.cc +finally_cxx_test_LDADD = libfintest.a +TESTS += finally-cxx-test +endif + +###----- That's all, folks -------------------------------------------------- diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..f1c0dc1 --- /dev/null +++ b/configure.ac @@ -0,0 +1,152 @@ +dnl -*-autoconf-*- +dnl +dnl Configuration script for `finally' +dnl +dnl (c) 2023 Mark Wooding +dnl + +dnl----- Licensing notice --------------------------------------------------- +dnl +dnl This file is part of the `Finally' package. +dnl +dnl Finally is free software: you can redistribute it and/or modify it +dnl under the terms of the GNU Library General Public License as published +dnl by the Free Software Foundation; either version 2 of the License, or +dnl (at your option) any later version. +dnl +dnl Finally is distributed in the hope that it will be useful, but WITHOUT +dnl ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +dnl FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public +dnl License for more details. +dnl +dnl You should have received a copy of the GNU Library General Public +dnl License along with Finally. If not, write to the Free Software +dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +dnl USA. + +dnl-------------------------------------------------------------------------- +dnl Initialization. + +AC_INIT([finally], [0.9], [mdw@distorted.org.uk]) +AC_CONFIG_SRCDIR([finally.h]) +AC_CONFIG_AUX_DIR([config]) +AC_CONFIG_MACRO_DIR([m4]) +AM_INIT_AUTOMAKE([foreign]) +AC_REQUIRE_AUX_FILE([tap-driver.sh]) +AM_SILENT_RULES([yes]) + +dnl-------------------------------------------------------------------------- +dnl C programming environment. + +AC_PROG_CC +AM_PROG_CC_C_O +AC_SUBST(AM_CFLAGS) + +case $GCC in + yes) + AM_CFLAGS="$AM_CFLAGS -std=c89 -pedantic -Wall -Wextra -Werror" + ;; +esac + +dnl-------------------------------------------------------------------------- +dnl C++ programming environment. + +AC_PROG_CXX +AC_SUBST(AM_CXXFLAGS) + +case $GXX in + yes) + AM_CXXFLAGS="$AM_CXXFLAGS -std=c++11 -pedantic -Wall -Wextra -Werror" + ;; +esac + +AC_DEFUN([FINALLY_CXX14_PROGRAM], [AC_LANG_PROGRAM([], [ +#if !defined(__cplusplus) || __cplusplus < 201103 + choke me +#endif +])]) + +AC_CACHE_CHECK([whether $CXX works at all], [finally_cv_cxx_works_p], [ + AC_LANG_PUSH([C++]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [])], + [finally_cv_cxx_works_p=yes], + [finally_cv_cxx_works_p=no]) + AC_LANG_POP([C++])]) + +case $finally_cv_cxx_works_p in + yes) + AC_CACHE_CHECK([whether $CC accepts \`-fexceptions'], + [finally_cv_gcc_fexceptions_p], [ + AC_LANG_PUSH([C]) + finally_test_original_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS -fexceptions" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [])], + [finally_cv_gcc_fexceptions_p=yes], + [finally_cv_gcc_fexceptions_p=no]) + CFLAGS=$finally_test_original_CFLAGS + AC_LANG_POP([C])]) + ;; + no) + finally_cv_gcc_fexceptions_p=no + ;; +esac + +case $finally_cv_cxx_works_p in + yes) + AC_CACHE_CHECK([whether $CXX supports C++11], [finally_cv_cxx14_p], [ + AC_LANG_PUSH([C++]) + AC_COMPILE_IFELSE([FINALLY_CXX14_PROGRAM], + [finally_cv_cxx14_p=yes], + [finally_cv_cxx14_p=no]) + AC_LANG_POP([C++])]) + ;; + *) + finally_cv_cxx14_p=no + CXX=\${CC} + ;; +esac + +dnl-------------------------------------------------------------------------- +dnl Other tools. + +AC_CHECK_PROGS([autom4te]) +AC_PROG_RANLIB + +dnl-------------------------------------------------------------------------- +dnl Test configuration. + +finally_test_original_CFLAGS=$CFLAGS +CFLAGS="$CFLAGS $AM_CFLAGS" +case $finally_cv_gcc_fexceptions_p in + yes) CFLAGS="$CFLAGS -fexceptions" ;; +esac +FINALLY_CHECK +CFLAGS=$finally_test_original_CFLAGS +AM_CFLAGS="$AM_CFLAGS $FINALLY_CFLAGS" +AM_CONDITIONAL([C], [test $finally_flavour != NIL]) +AM_CONDITIONAL([CXX], [test $finally_cv_cxx_works_p = yes]) +AM_CONDITIONAL([CXX14], [test $finally_cv_cxx14_p = yes]) + +case $finally_flavour,$finally_cv_cxx14_p in + NIL,no) + AC_MSG_ERROR([no suitable C or C++ compiler found: not going to space today]) + ;; +esac + +case $finally_flavour,$finally_cv_gcc_fexceptions_p,$finally_cv_cxx_works_p in + NIL,*,no) finally_fexceptions_p=no ;; + *,yes,*) AM_CFLAGS="$AM_CFLAGS -fexceptions"; finally_fexceptions_p=yes ;; + *,yes) finally_fexceptions_p=yes ;; +esac +case $finally_fexceptions_p in + yes) AC_DEFINE([HAVE_FEXCEPTIONS], [1]) ;; +esac +AM_CONDITIONAL([FEXCEPTIONS], [test $finally_fexceptions_p = yes]) + +dnl-------------------------------------------------------------------------- +dnl Output. + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT + +dnl----- That's all, folks -------------------------------------------------- diff --git a/examine-binary b/examine-binary new file mode 100755 index 0000000..acae4e6 --- /dev/null +++ b/examine-binary @@ -0,0 +1,37 @@ +#! /bin/sh -e + +echo "1..1" +env | sort | sed 's/^/# /' + +check_exe () { + tid=$1 exe=$2 + if ! [ -f $exe ]; then + echo "ok $tid # skip executable not found"; return; fi + objty=$(objdump -f $exe | sed -n '/^.*: *file format \(.*\)/s//\1/p') + echo "# $exe object type $objty" + case $objty in + elf32-* | elf64-*) + if f=$(objdump -p $exe | + sed -n '/^ *STACK / { n; s/^.*flags *\([-rwx]*\).*/\1/p; }') + then + echo "# STACK segment flags $f" + case $f in + rw-) echo "ok $tid stack not executable" ;; + rwx) echo "not ok $tid executable stack" ;; + *) echo "not ok $tid unexpected stack-segment flags" ;; + esac + else + echo "not ok $tid failed to find stack-segment flags" + fi + ;; + pei-x86-64) + if nm $exe | grep __enable_execute_stack; then + echo "not ok $tid # found call to __enable_execute_stack" + else + echo "ok $tid # stack maybe not executable" + fi + ;; + esac +} + +check_exe 1 finally-test diff --git a/finally-cxx-test.cc b/finally-cxx-test.cc new file mode 100644 index 0000000..b01ce89 --- /dev/null +++ b/finally-cxx-test.cc @@ -0,0 +1,29 @@ +/* -*-c++-*- + * + * Test program for using `finally.h' from C++ + * + * (c) 2023 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the `Finally' package. + * + * Finally is free software: you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Finally 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 Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Finally. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/* Let's just use the usual C code. */ +#include "finally-test.c" diff --git a/finally-test.c b/finally-test.c new file mode 100644 index 0000000..e95f16b --- /dev/null +++ b/finally-test.c @@ -0,0 +1,163 @@ +/* -*-c-*- + * + * Test program for using `finally.h' + * + * (c) 2023 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the `Finally' package. + * + * Finally is free software: you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Finally 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 Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Finally. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/* Attention! + * + * This file is also compiled as C++, so we must be careful to keep it in the + * common subset of C and C++. Anything language-specific needs to be split + * off into its own separate source file. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include + +#include "finally.h" +#include "finally-test.h" + +/*----- Main code ---------------------------------------------------------*/ + +static int test_softball(void) +{ + FINALLY({ STEP(1); }); + + STEP(0); + return (2); +} + +static int test_ordering(void) +{ + FINALLY({ STEP(2); }); + FINALLY({ STEP(1); }); + + STEP(0); + + return (3); +} + +static int test_capture(void) +{ + int n = -1; FINALLY({ STEP(n); }); + + STEP(0); + n = 1; return (2); +} + +static int test_internal_block(void) +{ + int i; + FINALLY({ STEP(10); }); + + for (i = 0; i < 5; i++) { + FINALLY({ STEP(2*i + 1); }); + STEP(2*i); + } + return (11); +} + +static int test_local_xfer(void) +{ + int i, j; + + STEP(0); + + { FINALLY({ STEP(2); }); STEP(1); } + + do { + FINALLY({ STEP(4); }); + STEP(3); + if (secretly_true) break; + MISSTEP; + } while (0); + + for (i = 0; i < 5; i++) { + FINALLY({ + if (i == 3) STEP(46); + else STEP(12*i + 16); + }); + STEP(12*i + 5); + for (j = 0; j < 5; j++) { + FINALLY({ STEP(12*i + 2*j + 7); }); + if (i == 3 && j == 1) { STEP(44); goto escape; } + else if (j != 3) STEP(12*i + 2*j + 6); + else { FINALLY({ STEP(12*i + 2*j + 6); }); continue; } + } + } +escape: + STEP(47); + + return (48); +} + +#if defined(HAVE_FEXCEPTIONS) +void try_catch_filling(unsigned f) +{ + int outstep = f&TCF_THROW ? 12 : 5; FINALLY({ STEP(outstep); }); + + if (f&TCF_THROW) STEP(10); + else STEP(2); + + try_catch_inner(f); + STEP(4); +} + +static int test_try_catch(void) +{ + STEP(0); + try_catch_outer(0); + STEP(8); + try_catch_outer(TCF_THROW); + return (15); +} +#endif + +int main(void) +{ + init_test(); + +#define RUNTEST(name) \ + do { begin_test(#name); STEP(test_##name()); end_test(); } while (0) +#define SKIPTEST(name, excuse) skip_test(#name, excuse) + + RUNTEST(softball); + RUNTEST(ordering); + RUNTEST(local_xfer); + RUNTEST(capture); + RUNTEST(internal_block); +#if defined(HAVE_FEXCEPTIONS) + RUNTEST(try_catch); +#else + SKIPTEST(try_catch, "no C++ compiler or no exception-handling support"); +#endif + +#undef RUNTEST +#undef SKIPTEST + + return (test_report()); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/finally-test.h b/finally-test.h new file mode 100644 index 0000000..c2c4693 --- /dev/null +++ b/finally-test.h @@ -0,0 +1,70 @@ +/* -*-c-*- + * + * Definitions for testing + * + * (c) 2023 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the `Finally' package. + * + * Finally is free software: you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Finally 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 Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Finally. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef FINALLY_TEST_H +#define FINALLY_TEST_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Utility macros ----------------------------------------------------*/ + +#define STR(x) STR1(x) +#define STR1(x) #x +#define WHERE __FILE__ ":" STR(__LINE__) + +/*----- Strange hacks -----------------------------------------------------*/ + +extern const int secretly_true; + +/*----- Functions provided ------------------------------------------------*/ + +#define STEP(s) check_step(s, WHERE) +#define MISSTEP STEP(-1) + +extern void init_test(void); +extern void begin_test(const char *name); +extern void check_step(int s, const char *where); +extern void end_test(void); +extern void skip_test(const char *name, const char *excuse); +extern int test_report(void); + +#if defined(HAVE_FEXCEPTIONS) +# define TCF_THROW 1u + extern void try_catch_outer(unsigned f); + extern void try_catch_filling(unsigned f); + extern void try_catch_inner(unsigned f); +#endif + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/finally.h b/finally.h new file mode 100644 index 0000000..8488161 --- /dev/null +++ b/finally.h @@ -0,0 +1,186 @@ +/* -*-c-*- + * + * Arrange to have code executed when a function ends + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the `Finally' package. + * + * Finally is free software: you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Finally 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 Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Finally. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef FINALLY_H +#define FINALLY_H + +/*----- Compatibility machinery -------------------------------------------*/ + +/* Some preliminary hacks for detecting compiler versions. */ +#ifdef __GNUC__ +# define FINALLY__GCC_P(maj, min) \ + (__GNUC__ > (maj) || (__GNUC__ == (maj) && __GNUC_MINOR__ >= (min))) +#else +# define FINALLY__GCC_P(maj, min) 0 +#endif + +#ifdef __clang__ +# define FINALLY__CLANG_P(maj, min) \ + (__clang_major__ > (maj) || (__clang_major__ == (maj) && \ + __clang_minor__ >= (min))) +#else +# define FINALLY__CLANG_P(maj, min) 0 +#endif + +/* If configuration machinery hasn't determined a flavour, then we'll take a + * rough guess based on compiler versions. + */ +#if !defined(FINALLY_CONFIG_FLAVOUR) && \ + !defined(__clang__) && FINALLY__GCC_P(3, 3) +# define FINALLY_CONFIG_FLAVOUR GCC_NESTED_FUNCTIONS +#endif + +#if !defined(FINALLY_CONFIG_FLAVOUR) +# define FINALLY_CONFIG_FLAVOUR NIL +#endif + +/*----- Macros provided ---------------------------------------------------*/ + +/* Before we start, a note about compatibility. We're using pretty esoteric + * compiler features here, and not all compilers support them. I'm most + * interested in GCC, which will work fine. This isn't going to work for + * other compilers, but lots of them try to impersonate GCC, and it's just + * not worth the effort to try to see through their lies. + * + * So the rules are: if you include this header file, you've either already + * made an effort to check that it's likely to work (e.g., by using the + * provided Autoconf macro), or you're willing to put up with whatever + * wreckage you end up with if the compiler doesn't actually like what we're + * saying here. + */ + +/* And a utility for pasting tokens after macro expansion. */ +#define FINALLY__GLUE(x, y) FINALLY__DOGLUE(x, y) +#define FINALLY__DOGLUE(x, y) x##y + +/* Now, we need a way to make a temporary name which isn't likely to conflict + * with anything else. + */ +#define FINALLY__TMP(name) FINALLY__GLUE(_finally__##name##__, __LINE__) + +/* And some other tricks which we may or may not need. */ +#if defined(__cplusplus) && __cplusplus >= 201703 +# define FINALLY__IGNORABLE fucksocks +#elif FINALLY__GCC_P(2, 5) || FINALLY__CLANG_P(3, 3) +# define FINALLY__IGNORABLE __attribute__((__unused__)) +#endif +#ifndef FINALLY__IGNORABLE +# define FINALLY__IGNORABLE +#endif + +/* Flavour selection machinery. */ +#define FINALLY__FLAVOUR_NIL -1 +#define FINALLY__FLAVOUR_GCC_NESTED_FUNCTIONS 1 + +#define FINALLY__SELECTED_FLAVOUR \ + FINALLY__GLUE(FINALLY__FLAVOUR_, FINALLY_CONFIG_FLAVOUR) + +/* @FINALLY(code)@ + * @FINALLY_TAGGED(tag, code)@ + * + * This macro may be placed anywhere within a function where a declaration is + * acceptable -- so at the head of a block in C89, or anywhere between + * statements in C99 or later. It causes @code@ to be executed when control + * leaves the enclosing scope. If it's placed at top-level within a + * function, for example, then the @code@ is executed when the function + * returns. + * + * There may be multiple @FINALLY@ invocations within a scope; they are + * executed in reverse order when control leaves the scope. + * + * Due to technical limitations, it's forbidden to have two @FINALLY@ + * invocations on the same source line. This would seem to stymie writing a + * macro which expands to two invocations of @FINALLY@, which would be useful + * if those invocations were to end up in different scopes. Such a macro can + * still be written using @FINALLY_TAGGED@ instead, which takes an additional + * @tag@ argument, which may be any identifier: the rule then becomes that a + * source line may not contain two invocations of @FINALLY_TAGGED@ bearing + * the same @tag@. (It would be possible to overcome this limitation using + * the @__COUNTER__@ GCC extension, but I'd prefer not to limit the potential + * portability of this feature even further by insisting that something + * analogous exist in every supported compiler, and it would do users a + * disservice to create portability problems by having different rules on + * different compilers.) + */ +#if defined(__cplusplus) && __cplusplus >= 201103 + /* Oooh, we're being compiled by a C++ compiler. There's no need to deploy + * nonportable tricks, because we have an insane language that can already + * do what we need. + */ + + namespace finally { + template class Finally { + F fn; + public: + Finally(F f) : fn{f} { ; } + ~Finally() { fn(); } + }; + template Finally finally(F &&fn) + { return Finally(fn); } + } + +# define FINALLY_TAGGED(tag, body) \ + FINALLY__IGNORABLE auto FINALLY__TMP(tag##__var) = \ + finally::finally([&]{ body }) + +#elif FINALLY__GLUE(FINALLY__FLAVOUR_, FINALLY_CONFIG_FLAVOUR) == \ + FINALLY__FLAVOUR_GCC_NESTED_FUNCTIONS + /* We're being compiled by GCC, or by something which wants to be mistaken + * for GCC at any rate. And GCC has nested functions. So, for each + * block, we'll define a dummy variable that we don't care about, and + * attach a nested function as its cleanup handler which will execute our + * cleanup code. + */ + +# define FINALLY_TAGGED(tag, body) \ + __extension__ __inline__ \ + void FINALLY__TMP(tag##__fn) \ + (const int __attribute__((__unused__)) *_finally__hunoz) \ + { body } \ + __attribute__((__unused__, __cleanup__(FINALLY__TMP(tag##__fn)))) \ + int FINALLY__TMP(tag##__var) + +#elif FINALLY__GLUE(FINALLY__FLAVOUR_, FINALLY_CONFIG_FLAVOUR) == \ + FINALLY__GLUE(FINALLY__FLAVOUR_, NIL) + /* We don't have a flavour to support this environment. */ + +# error "Compiler not supported. This isn't going to work." +#else + /* This case analysis should be exhaustive. */ + +# error "Internal error: `FINALLY_CONFIG_FLAVOUR' bungled." +#endif + +/* We now have `FINALLY_TAGGED'; defining `FINALLY' is easy. The TAG here is + * guaranteed not conflict with any call on `FINALLY_TAGGED', since TAGs are + * required to be identifiers. + */ +#define FINALLY(code) FINALLY_TAGGED(0, code) + +/*----- That's all, folks -------------------------------------------------*/ + +#endif diff --git a/m4/finally.m4 b/m4/finally.m4 new file mode 100644 index 0000000..70c2d83 --- /dev/null +++ b/m4/finally.m4 @@ -0,0 +1,135 @@ +dnl -*-autoconf-*- + +### SYNOPSIS +### +### FINALLY([IF-SUCCEEDED], [IF-FAILED]) +### +### DESCRIPTION +### +### Probe at the C compiler to determine how, if at all, to implement the +### `FINALLY' macro, which arranges to run some code when control leaves a +### given scope. This isn't at all a standard C feature, so we need to use +### compiler-specific hacks, and this is the main machinery for deciding +### which hacks to deploy. +### +### On exit, the shell variable `finally_flavour' is set to an uppercase +### word naming the chosen implementation strategy: it will be `NIL' if the +### macro failed and no strategy could be found. The preprocessor define +### `FINALLY_CONFIG_FLAVOUR' is set to `FINALLY_CONFIG_FLAVOUR_...' +### followed by the same word: this is the main input to the selection +### machinery in `finally.h'. +### +### The substitution variables `FINALLY_CFLAGS' and `FINALLY_LIBS' are set +### to any additional compiler flags or libraries needed to support the +### `FINALLY' macro. They can be set per-target in the `Makefile', or +### stuffed into the global variables by the `configure' script. +### +### If the macro managed to find a workable strategy, then the shell +### fragment IF-SUCCEEDED is run; otherwise, (if `finally_flavour' is +### `NIL'), the shell fragment IF-FAILED is run. +### +### LICENSE +### +### Copyright (c) 2023 Mark Wooding +### +### 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, see . +### +### In particular, no exception to the GPL is granted regarding generated +### `configure' scripts which are the output of Autoconf. + +AC_DEFUN([FINALLY_GCC_NESTED_FUNCTIONS_TEST_PROGRAM], [AC_LANG_PROGRAM([], [ + __extension__ __inline__ void nested(void) { ; } + nested(); +])]) +AC_DEFUN([FINALLY_GCC_ATTRIBUTE_CLEANUP_TEST_PROGRAM], [AC_LANG_PROGRAM([ + extern void cleanup_fn(const int *x); + extern void bamboozle(int *x_inout); +], [ + __attribute__((cleanup(cleanup_fn))) int x = 0; + bamboozle(&x); +])]) + +dnl Decide whether we can define a plausible `FINALLY' macro. +AC_DEFUN([FINALLY_CHECK], +[finally_flavour=undecided finally_result="not supported" + +dnl We're going to want to test C code. +AC_LANG_PUSH([C]) + +case $finally_flavour,$GCC in + undecided,yes) + dnl Our GCC-ish strategies have a common factor: they depend on + dnl `__attribute__((cleanup(...)))' working. So let's check for that. + + AC_CACHE_CHECK([whether the alleged GNU C compiler supports \`__attribute__((cleanup(...)))'], + [finally_cv_gcc_attribute_cleanup_p], [ + AC_COMPILE_IFELSE([FINALLY_GCC_ATTRIBUTE_CLEANUP_TEST_PROGRAM], + [finally_cv_gcc_attribute_cleanup_p=yes], + [finally_cv_gcc_attribute_cleanup_p=no])]) + case $finally_cv_gcc_attribute_cleanup_p in + no) finally_flavour=NIL ;; + esac + ;; +esac + +case $finally_flavour,$GCC in + undecided,yes) + dnl Autoconf has decided that the compiler smells a bit like GCC, and it + dnl certainly seems to support a GCC extension. But many compilers + dnl impersonate GCC, in more or less convincing ways. Our GCC-flavoured + dnl `FINALLY' code depends on nested functions, which GCC has supported + dnl pretty much forever, but other compilers don't even though they lie + dnl about being compatible. + + AC_CACHE_CHECK([whether the alleged GNU C compiler supports nested functions], + [finally_cv_gcc_nested_functions_p], [ + AC_COMPILE_IFELSE([FINALLY_GCC_NESTED_FUNCTIONS_TEST_PROGRAM], + [finally_cv_gcc_nested_functions_p=yes], + [finally_cv_gcc_nested_functions_p=no])]) + case $finally_cv_gcc_nested_functions_p in + yes) + finally_flavour=GCC_NESTED_FUNCTIONS + finally_result="GCC nested functions" + ;; + esac + ;; +esac + +case $finally_flavour in + undecided) + dnl We've got this far and we've drawn a blank. Give up. + finally_flavour=NIL + ;; +esac + +AC_LANG_POP([C]) + +dnl Pass the results on to the implementation machinery. +AC_MSG_CHECKING([how to implement deferred cleanup code]) +AC_DEFINE_UNQUOTED([FINALLY_CONFIG_FLAVOUR], + [$finally_flavour], + [Select one of the implementation strategies for the `FINALLY' macro.]) +AC_SUBST(FINALLY_CFLAGS) AC_SUBST(FINALLY_LIBS) +AC_MSG_RESULT([$finally_result]) + +dnl Invoke the caller's shell fragments according to our findings. +case $finally_flavour in + nil) + $2 + ;; + *) + $1 + ;; +esac +]) diff --git a/test-guts.c b/test-guts.c new file mode 100644 index 0000000..b2926e9 --- /dev/null +++ b/test-guts.c @@ -0,0 +1,135 @@ +/* -*-c-*- + * + * Main test machinery + * + * (c) 2023 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the `Finally' package. + * + * Finally is free software: you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Finally 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 Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Finally. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include + +#include "finally-test.h" + +/*----- Global variables --------------------------------------------------*/ + +const int secretly_true = 1; + +/*----- Static variables --------------------------------------------------*/ + +static const char *curr; +static unsigned nrun = 0, nskip = 0, nlose = 0; +static unsigned flags = 0; +enum { HUMAN, TAP }; static unsigned outform = HUMAN; +#define f_failthis 1u + +static int step; + +/*----- Main code ---------------------------------------------------------*/ + +void init_test(void) +{ + const char *p; + + p = getenv("TEST_OUTFORM"); + if (!p || strcmp(p, "human") == 0) outform = HUMAN; + else if (strcmp(p, "tap") == 0) outform = TAP; + else { fprintf(stderr, "unknown output format `%s'\n", p); exit(2); } +} + +void begin_test(const char *name) + { step = 0; flags &= ~f_failthis; curr = name; } + +void check_step(int s, const char *where) +{ + FILE *fp; + + if (step != s) { + switch (outform) { + case HUMAN: fp = stderr; break; + case TAP: fp = stdout; fputs("# ", stdout); break; + default: abort(); + } + fprintf(fp, "%s (%s): misstep: expected %d but found %d\n", + where, curr, step, s); + flags |= f_failthis; + } + step++; +} + +void end_test(void) +{ + nrun++; if (flags&f_failthis) nlose++; + + switch (outform) { + case HUMAN: + printf("%s: %s\n", curr, flags&f_failthis ? "FAILED" : "ok"); + break; + case TAP: + printf("%s %u %s\n", flags&f_failthis ? "not ok" : "ok", nrun, curr); + break; + default: + abort(); + } +} + +void skip_test(const char *name, const char *excuse) +{ + nrun++; nskip++; + + switch (outform) { + case HUMAN: printf("%s: (skipped: %s)\n", name, excuse); break; + case TAP: printf("ok %u %s # skip (%s)\n", nrun, name, excuse); break; + default: abort(); + } +} + +int test_report(void) +{ +#define TESTS(n) ((n) == 1 ? "test" : "tests") + + switch (outform) { + case HUMAN: + if (nlose) + printf("FAILED %u out of %u %s", nlose, nrun, TESTS(nrun)); + else if (nskip) + printf("passed %u out of %u %s", + nrun - nlose - nskip, nrun, TESTS(nrun)); + else + printf("passed all %u %s", nrun, TESTS(nrun)); + if (nskip) printf(" (skipped %u %s)", nskip, TESTS(nskip)); + putchar('\n'); + return (nlose ? 2 : 0); + case TAP: + printf("1..%u\n", nrun); + return (0); + default: + abort(); + } + +#undef TESTS +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/try-catch.cc b/try-catch.cc new file mode 100644 index 0000000..488db12 --- /dev/null +++ b/try-catch.cc @@ -0,0 +1,53 @@ +/* -*-c++-*- + * + * Check handling of exceptions + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the `Finally' package. + * + * Finally is free software: you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Finally 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 Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Finally. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "finally-test.h" + +/*----- Main code ---------------------------------------------------------*/ + +class ball { }; + +void try_catch_inner(unsigned f) +{ + if (f&TCF_THROW) STEP(11); + else STEP(3); + if (f&TCF_THROW) throw ball(); +} + +void try_catch_outer(unsigned f) +{ + if (f&TCF_THROW) STEP(9); + else STEP(1); + try { try_catch_filling(f); STEP(6); } + catch (ball) { STEP(13); } + if (f&TCF_THROW) STEP(14); + else STEP(7); +} + +/*----- That's all, folks -------------------------------------------------*/