Longjmp и RAII

Итак, у меня есть библиотека (не написанная мной), которая, к сожалению, использует abort() для устранения определенных ошибок. На уровне приложения эти ошибки могут быть восстановлены, поэтому я хотел бы обработать их, а не видеть, как пользователь сталкивается с сбоем. Поэтому я в конечном итоге пишу код следующим образом:

static jmp_buf abort_buffer;
static void abort_handler(int) {
    longjmp(abort_buffer, 1); // perhaps siglongjmp if available..
}

int function(int x, int y) {

    struct sigaction new_sa;
    struct sigaction old_sa;

    sigemptyset(&new_sa.sa_mask);
    new_sa.sa_handler = abort_handler;
    sigaction(SIGABRT, &new_sa, &old_sa);

    if(setjmp(abort_buffer)) {
        sigaction(SIGABRT, &old_sa, 0);
        return -1
    }

    // attempt to do some work here
    int result = f(x, y); // may call abort!

    sigaction(SIGABRT, &old_sa, 0);
    return result;
}

Не очень элегантный код. Поскольку этот шаблон заканчивается необходимостью повторять в нескольких точках кода, я хотел бы немного упростить его и, возможно, обернуть его в объект многократного использования. Моя первая попытка включает использование RAII для обработки установки/отрыва обработчика сигнала (это необходимо сделать, потому что каждая функция требует различной обработки ошибок). Поэтому я придумал это:

template <int N>
struct signal_guard {
    signal_guard(void (*f)(int)) {
        sigemptyset(&new_sa.sa_mask);
        new_sa.sa_handler = f;
        sigaction(N, &new_sa, &old_sa);
    }

    ~signal_guard() {
        sigaction(N, &old_sa, 0);
    }
private:
    struct sigaction new_sa;
    struct sigaction old_sa;
};


static jmp_buf abort_buffer;
static void abort_handler(int) {
    longjmp(abort_buffer, 1);
}

int function(int x, int y) {
    signal_guard<SIGABRT> sig_guard(abort_handler);

    if(setjmp(abort_buffer)) {
        return -1;
    }

    return f(x, y);
}

Конечно, тело function более намного проще и понятнее, но сегодня утром мне пришла в голову мысль. Гарантировано ли это? Здесь мои мысли:

  • Никакие переменные не изменяются или изменяются между вызовами setjmp/longjmp.
  • Я longjmp привязываюсь к местоположению в том же стеке стека, что и setjmp и return, как обычно, поэтому я разрешаю коду выполнять код очистки, который компилятор испускал в точках выхода функция.
  • Кажется, он работает как ожидалось.

Но у меня все еще возникает ощущение, что это, скорее всего, поведение undefined. Что вы, ребята, думаете?

Ответ 1

Я предполагаю, что f находится в сторонней библиотеке/приложении, потому что иначе вы могли бы просто исправить это, чтобы не вызывать прерывание. Учитывая это, и что RAII может или не может надежно производить правильные результаты на всех платформах/компиляторах, у вас есть несколько вариантов.

  • Создайте крошечный общий объект, который определяет abort и LD_PRELOAD. Затем вы контролируете, что происходит при прерывании, а НЕ в обработчике сигналов.
  • Запустите f внутри подпроцесса. Затем вы просто проверяете код возврата, и если он не повторил попытку с обновленными вводами.
  • Вместо использования RAII просто позвоните своему оригинальному function из нескольких точек вызова и дайте ему вручную выполнить настройку/разрывы явно. В этом случае он по-прежнему исключает копирование.

Ответ 2

Мне действительно нравится ваше решение и закодировано что-то подобное в тестовых жгутах, чтобы проверить, что целевая функция assert() как ожидалось.

Я не вижу причин, по которым этот код вызывает поведение undefined. Стандарт C, похоже, благословляет его: обработчики, являющиеся результатом abort(), освобождаются от ограничения вызова функций библиотеки из обработчика. (Предостережение: это 7.14.1.1 (5) из C99 - к сожалению, у меня нет копии C90, версии, на которую ссылается стандарт С++).

С++ 03 добавляет дополнительное ограничение: если какие-либо автоматические объекты будут уничтожены с помощью управления передачей исключенного исключения на другую (целевую) точку в программе, тогда вызов longjmp (jbuf, val) в точке бросания управление передачей в ту же (целевую) точку имеет поведение undefined. Я предполагаю, что ваше утверждение о том, что "никакие переменные не изменчивы или не изменяется между вызовами в setjmp/longjmp", включает в себя создание экземпляров любых автоматических объектов С++. (Я предполагаю, что это некоторая библиотека наследия C?).

Не является безопасным сигналом асинхронного сигнала POSIX (или его отсутствием), проблема - abort() синхронно генерирует SIGABRT с выполнением программы.

Самая большая проблема будет искажать глобальное состояние стороннего кода: маловероятно, что автор приложит все усилия, чтобы согласовать состояние до abort(). Но, если вы правы, что переменные не меняются, это не проблема.

Если кто-то, кто лучше разбирается в стандартах, может доказать, что я ошибаюсь, я буду благодарен за просвещение.