try/catch in C [PHP interpreter]

Abstract

PHP interpreter is written in C, but uses try/catch construct known from other popular languages. This approach is different from everyday C-like error handling, where it's done by using function return value or by modifying error variable passed as an argument to a function.

    zend_try {
        PG(during_request_startup) = 1;
        php_output_activate(TSRMLS_C);
       /* unimportant stuff */
    } zend_catch {
        retval = FAILURE;
    } zend_end_try();

If one of instructions in a "try" block raises an exception then the evaluation of this block is terminated and execution flow moves to a "catch" block.

How it works?

The trick lie in use of setjmp, longjonp and sigsetjmp from setjmp.h. On some systems it's preferred to use theirs replacements: sigsetjmp, siglongjmp, sigjmp_buf. This nuance was hidden in below macros (Zend/zend.h):

#include <setjmp.h>

#ifdef HAVE_SIGSETJMP
# define SETJMP(a) sigsetjmp(a, 0)
# define LONGJMP(a,b) siglongjmp(a, b)
# define JMP_BUF sigjmp_buf
#else
# define SETJMP(a) setjmp(a)
# define LONGJMP(a,b) longjmp(a, b)
# define JMP_BUF jmp_buf
#endif

It breaks Rule 20.7 of MISRA C coding standard (setjmp and longjmp shall not be used). In Zend/zend.h are defined macros mentioned in the first listing:

#define zend_try                                                \
    {                                                           \
        JMP_BUF *__orig_bailout = EG(bailout);                  \
        JMP_BUF __bailout;                                      \
                                                                \
        EG(bailout) = &__bailout;                               \
        if (SETJMP(__bailout)==0) {
#define zend_catch                                              \
        } else {                                                \
            EG(bailout) = __orig_bailout;
#define zend_end_try()                                          \
        }                                                       \
        EG(bailout) = __orig_bailout;                           \
    }

It breaks Rule 19.4 of MISRA C coding standard (all brackets in a macro should be balanced). EG points us to Zend/zend.c that shows how to rise exceptions:

BEGIN_EXTERN_C()
ZEND_API void _zend_bailout(char *filename, uint lineno) /* {{{ */
{
    TSRMLS_FETCH();

    if (!EG(bailout)) {
        zend_output_debug_string(1, "%s(%d) : Bailed out without a bailout address!", filename, lineno);
        exit(-1);
    }
    CG(unclean_shutdown) = 1;
    CG(in_compilation) = EG(in_execution) = 0;
    EG(current_execute_data) = NULL;
    LONGJMP(*EG(bailout), FAILURE);
}

I isolated this code and created my own application that uses it. It can be downloaded by cloning git@github.com:RobertGawron/snippets.git (ExceptionHandlingInC directory), as in original version, it can also handle nested try/catch constructions.

What's your opinion about this idea? I think that it shouldn't be used because the code is less readable, in addition I don't like macros and I try to avoid them if possible.

3 comments:

  1. php never stops to amaze me (in negative sense ofc)

    ReplyDelete
    Replies
    1. I agree, IMO above trick is pointless and obfuscate the code.

      Delete
  2. Tienes una gran habilidad para explicar las cosas de una manera identificable, ¡es como si estuvieras leyendo mi mente! Estaba luchando por encontrar un rastreador de clics preciso hasta que leí su publicación y me enteré de Contador De clicks
    . ¡Me ha ahorrado mucho tiempo!

    ReplyDelete