Мне кажется, я помню, что доверять значению $@
после eval
небезопасно. Что-то о том, что у обработчика сигнала есть шанс установить $@
до того, как вы его увидите, или что-то в этом роде. Я также слишком устал и ленив прямо сейчас, чтобы найти настоящую причину. Итак, почему доверять $@
небезопасно?
Почему `$ @` ненадежен?
Ответы (3)
В Try::Tiny
perldoc есть исчерпывающее обсуждение проблемы с $@
:
Есть ряд проблем с eval.
Убийство $ @
Когда вы запускаете блок eval и он успешен, $ @ будет очищен, что потенциально может привести к обнаружению ошибки, которая в настоящее время обнаруживается.
Это вызывает действие на расстоянии, устраняя предыдущие ошибки, которые ваш вызывающий абонент, возможно, еще не обработал.
Чтобы избежать этой проблемы, перед вызовом eval необходимо правильно локализовать $ @.
В частности, $ @ затирается в начале eval, что также делает невозможным захват предыдущей ошибки до того, как вы умрете (например, при создании объектов исключений со стеками ошибок).
По этой причине try фактически установит $ @ в его предыдущее значение (до локализации) в начале блока eval.
Локализация $ @ автоматически маскирует ошибки
Внутри блока eval кубик ведет себя примерно так:
sub die { $@ = $_[0]; return_undef_from_eval(); }
Это означает, что если вы были вежливы и локализовали $ @, вы не можете умереть в этой области, или ваша ошибка будет отброшена (вместо этого вы напечатаете «Что-то не так»).
Обходной путь очень уродливый:
my $error = do { local $@; eval { ... }; $@; }; ... die $error;
$ @ может быть неверным значением
Этот код неверен:
if ( $@ ) { ... }
потому что из-за предыдущих предупреждений он мог быть отключен.
$ @ также может быть перегруженным объектом ошибки, который оценивается как false, но в любом случае это вызывает проблемы.
Классический режим отказа:
sub Object::DESTROY { eval { ... } } eval { my $obj = Object->new; die "foo"; }; if ( $@ ) { }
В этом случае, поскольку Object :: DESTROY не локализует $ @, но по-прежнему использует eval, он установит для $ @ значение "".
Деструктор вызывается, когда стек разматывается, после того, как die устанавливает $ @ в "foo at Foo.pm, строка 42 \ n", поэтому к моменту вычисления if ($ @) он был очищен eval в деструкторе.
Обходной путь для этого еще хуже, чем предыдущие. Несмотря на то, что мы не можем сохранить значение $ @ из кода, который не локализован, мы, по крайней мере, можем быть уверены, что eval был прерван из-за ошибки:
my $failed = not eval { ... return 1; };
Это потому, что eval, поймавший кубик, всегда будет возвращать ложное значение.
В Try :: Tiny довольно хороший список _1 _ / _ 2_ недостатки. Я думаю, вы, возможно, имеете в виду Локализация $ @ незаметно маскирует ошибки там.
$@
имеет те же проблемы, что и каждая глобальная переменная: когда что-то еще устанавливает ее, она сбрасывается во всей программе. Любой eval
может установить $@
. Даже если вы не видите рядом eval
, вы не знаете, кто еще может его вызвать (подпрограммы, связанные переменные и т. Д.).