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.

2 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