Неуловимый ChuckNorrisException

596

Можно ли построить фрагмент кода в Java , который сделал бы гипотетическую java.lang.ChuckNorrisExceptionнеуловимую?

Мысли, которые пришли в голову, используют, например, перехватчики или аспектно-ориентированное программирование .

Макс Чарас
источник
2
используя предложение из предоставленной ссылки @jschoen (отключите проверку байтового кода), вы можете выбросить что-то, что не расширяет Throwable! описано в моем ответе ниже.
Jtahlborn
4
Этот отрывок из ответа aioobe достаточно хорошо резюмирует связанный вопрос @jschoen: «То есть, ваш вопрос можно интерпретировать как« Если JVM отклоняется от спецификации, может ли он делать странные вещи, такие как бросание примитивов », и ответ, конечно, да."
Дэн возится с огнем
2
@Max - Можете ли вы рассказать о практическом использовании для этого?
Vineet Bhatia
3
как насчет исключения, которое восстанавливают себя на finalize()?
Ли Райан

Ответы:

314

Я не пробовал этого, поэтому я не знаю, будет ли JVM ограничивать что-то подобное, но, возможно, вы могли бы скомпилировать код, который генерирует ChuckNorrisException, но во время выполнения предоставьте определение класса, ChuckNorrisExceptionкоторое не расширяет Throwable .

ОБНОВИТЬ:

Не работает Это генерирует ошибку верификатора:

Exception in thread "main" java.lang.VerifyError: (class: TestThrow, method: ma\
in signature: ([Ljava/lang/String;)V) Can only throw Throwable objects
Could not find the main class: TestThrow.  Program will exit.

ОБНОВЛЕНИЕ 2:

На самом деле, вы можете заставить это работать, если отключите проверку байтового кода! ( -Xverify:none)

ОБНОВЛЕНИЕ 3:

Для тех, кто следует из дома, вот полный сценарий:

Создайте следующие классы:

public class ChuckNorrisException
    extends RuntimeException // <- Comment out this line on second compilation
{
    public ChuckNorrisException() { }
}

public class TestVillain {
    public static void main(String[] args) {
        try {
            throw new ChuckNorrisException();
        }
        catch(Throwable t) {
            System.out.println("Gotcha!");
        }
        finally {
            System.out.println("The end.");
        }
    }
}

Компилировать классы:

javac -cp . TestVillain.java ChuckNorrisException.java

Запустить:

java -cp . TestVillain
Gotcha!
The end.

Закомментируйте "extends RuntimeException" и перекомпилируйте ChuckNorrisException.javaтолько :

javac -cp . ChuckNorrisException.java

Запустить:

java -cp . TestVillain
Exception in thread "main" java.lang.VerifyError: (class: TestVillain, method: main signature: ([Ljava/lang/String;)V) Can only throw Throwable objects
Could not find the main class: TestVillain.  Program will exit.

Запустить без проверки:

java -Xverify:none -cp . TestVillain
The end.
Exception in thread "main"
jtahlborn
источник
18
Хорошо, а что если вы поймаете Objectвместо Throwable, тогда? (Компилятор не допустит этого, но, поскольку мы уже отключили верификатор, возможно, для этого можно взломать байт-код.)
Ilmari Karonen
11
В соответствии с тем, что вы можете бросить в Java, вы все равно можете ловить вещи, которые не расширяются, но бросать и ловить их - неопределенное поведение.
VolatileDream
8
@dzieciou Они могут быть правдой вместе. Возможно, вам удастся их перехватить, используя вашу версию среды Java в вашей конкретной версии операционной системы на вашем типе процессора. Но если в стандарте не указано, МОЖЕТ ли он быть пойман, это называется неопределенным поведением, потому что другие реализации Java могут сделать его неуловимым.
heinrich5991
2
Hmmph. Я надеялся, что для 176 upvotes вы написали некоторый код JNI, который обезьяны-исправляет весь стек вызовов, чтобы перебросить ваше исключение (вызванное ctor, конечно).
kdgregory
3
Делая все это, это также хорошая идея, чтобы встать на одну ногу, погладить свою голову и потер живот, в то время как свистящий Дикси ...;);)
Глен Бест
120

Обдумав это, я успешно создал неуловимое исключение. Однако я решил назвать его JulesWinnfield, а не Чака, потому что это одно исключение из грибовидных облаков. Кроме того, это может быть не совсем то, что вы имели в виду, но это, безусловно, не может быть пойман. Заметим:

public static class JulesWinnfield extends Exception
{
    JulesWinnfield()
    {
        System.err.println("Say 'What' again! I dare you! I double dare you!");
        System.exit(25-17); // And you shall know I am the LORD
    }
}


public static void main(String[] args)
{       
    try
    {
        throw new JulesWinnfield();
    } 
    catch(JulesWinnfield jw)
    {
        System.out.println("There's a word for that Jules - a bum");
    }
}

И вуаля! Неисследованное исключение.

Вывод:

запустить:

Скажи «Что» снова! Попробуй! Я дважды смею тебя!

Java Результат: 8

СТРОИТЬ УСПЕШНО (общее время: 0 секунд)

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

Также проверьте это:

public static class JulesWinnfield extends Exception
{
    JulesWinnfield() throws JulesWinnfield, VincentVega
    {
        throw new VincentVega();
    }
}

public static class VincentVega extends Exception
{
    VincentVega() throws JulesWinnfield, VincentVega
    {
        throw new JulesWinnfield();
    }
}


public static void main(String[] args) throws VincentVega
{

    try
    {
        throw new JulesWinnfield();
    }
    catch(JulesWinnfield jw)
    {

    }
    catch(VincentVega vv)
    {

    }
}

Вызывает переполнение стека - опять же, исключения остаются необработанными.

MikeTheLiar
источник
32
+1 за использование переполнения стека в вашем ответе. Шучу, действительно хороший ответ.
Иосия
7
Правильное «неуловимое исключение» будет гарантировать, что все включающие блоки finally будут выполняться без каких-либо промежуточных перехватов. Убийство системы не является исключением - оно просто убивает систему.
суперкат
4
Как вы "бросить" JulesWinfield? Разве система не остановится до того, как ее бросят?
Суперкат
6
@mikeTheLiar: система завершается во время конструктора, не так ли? Утверждение throw new Whatever()на самом деле состоит из двух частей: Whatever it = new Whatever(); throw it;и система умирает до того, как достигнет второй части.
Суперкат
5
@mikeTheLiar вы действительно можете поймать Жюля или Винсента довольно легко ... если вам удастся их бросить. Легко создать исключение, которое вы не можете выбросить:class cn extends exception{private cn(){}}
Джон Дворжак
85

С таким исключением, очевидно, будет обязательно использовать конструктор System.exit(Integer.MIN_VALUE);from, потому что это то, что произойдет, если вы бросите такое исключение;)

Корген
источник
32
+1; ИМО это единственно возможное решение. Неуловимое исключение должно завершить программу ...
home
7
Нет, этого не произойдет, когда вы выдадите такое исключение. Неполное исключение прервет один поток, оно не выйдет из jvm, в некоторых случаях само System.exit даже вызовет исключение SecurityException - не каждому фрагменту кода разрешено завершать работу программы.
josefx
3
Вы можете использовать while(true){}вместо System.exit().
Петр Прасмо
2
на самом деле, вы можете помешать System.exit()работе, установив менеджер безопасности, который запрещает это. это превратит конструктор в другое исключение (SecurityException), которое может быть перехвачено.
Jtahlborn
5
Хм, технически вы никогда не бросали исключения. Вы даже не сконструировали объект для броска!
Томас Эдинг
46

Любой код может поймать Throwable. Так что нет, какое бы исключение вы ни создавали, оно будет подклассом Throwable и может быть поймано.

Натан Хьюз
источник
11
Бросок был бы hangсам в попытке поймать ChuckNorrisException: P
PermGenError
35
public class ChuckNorrisException extends Exception {
    public ChuckNorrisException() {
        System.exit(1);
    }
}

(Конечно, технически это исключение никогда не генерируется, но правильное ChuckNorrisExceptionне может быть выброшено - оно выбрасывает вас первым.)

пушистый
источник
4
Мой коллега предложил использовать 'for (;;) {}', поскольку он чувствовал, что вызов System.exit (1) может вызвать исключение безопасности. Я голосую за творчество!
Фил стрит
Я согласен с окончанием вашего ответа. Никогда не связывайтесь с ChuckNorris, исключением или нет.
Бендж
28

Любое исключение, которое вы выбрасываете, должно расширять Throwable, чтобы его всегда можно было поймать. Так что ответ - нет.

Если вы хотите , чтобы сделать его трудно обрабатывать, вы можете переопределить методы getCause(), getMessage(), getStackTrace(), toString()чтобы бросить другие java.lang.ChuckNorrisException.

Mirelon
источник
2
Хм, ловить (Throwable t) вызывать какие-либо методы или иначе мутировать объект? Может быть возможно заставить предложение catch дополнительно генерировать исключение, делающее это невозможным.
Колтон
1
Я думаю, что catch(Throwable t)сохраняет только в переменную, поэтому мои предложения применяются только в следующем блоке, когда пользователь хочет справиться с исключением
mirelon 14.12.12
24

Мой ответ основан на идее @ jtahlborn, но это полностью работающая Java- программа, которую можно упаковать в файл JAR и даже развернуть на вашем любимом сервере приложений как часть веб-приложения. .

Прежде всего, давайте определим ChuckNorrisExceptionкласс так, чтобы он не приводил к краху JVM с самого начала (Чак действительно любит разбивать JVM, кстати)

package chuck;

import java.io.PrintStream;
import java.io.PrintWriter;

public class ChuckNorrisException extends Exception {

    public ChuckNorrisException() {
    }

    @Override
    public Throwable getCause() {
        return null;
    }

    @Override
    public String getMessage() {
        return toString();
    }

    @Override
    public void printStackTrace(PrintWriter s) {
        super.printStackTrace(s);
    }

    @Override
    public void printStackTrace(PrintStream s) {
        super.printStackTrace(s);
    }
}

Теперь идет Expendablesкласс, чтобы построить это:

package chuck;

import javassist.*;

public class Expendables {

    private static Class clz;

    public static ChuckNorrisException getChuck() {
        try {
            if (clz == null) {
                ClassPool pool = ClassPool.getDefault();
                CtClass cc = pool.get("chuck.ChuckNorrisException");
                cc.setSuperclass(pool.get("java.lang.Object"));
                clz = cc.toClass();
            }
            return (ChuckNorrisException)clz.newInstance();
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}

И, наконец, Mainкласс, чтобы пнуть задницу:

package chuck;

public class Main {

    public void roundhouseKick() throws Exception {
        throw Expendables.getChuck();
    }

    public void foo() {
        try {
            roundhouseKick();
        } catch (Throwable ex) {
            System.out.println("Caught " + ex.toString());
        }
    }

    public static void main(String[] args) {
        try {
            System.out.println("before");
            new Main().foo();
            System.out.println("after");
        } finally {
            System.out.println("finally");
        }
    }
}

Скомпилируйте и запустите его с помощью следующей команды:

java -Xverify:none -cp .:<path_to_javassist-3.9.0.GA.jar> chuck.Main

Вы получите следующий вывод:

before
finally

Не удивительно, ведь это удар с разворота :)

Wildfire
источник
очень хорошо! Я не так много сделал с манипулированием определением класса. Вам все еще нужно «проверить: нет» в командной строке?
Jtahlborn
@jtahlborn Да, попытка выбросить объект, не являющийся потомком Throwable, завершается неудачно без «verify: none».
Лесной пожар
о, у меня сложилось впечатление, что это как-то обошло это ограничение. так как это отличается от моего ответа?
Jtahlborn
2
Основное отличие состоит в том, что работает Java-код без взлома во время компиляции
Wildfire
15

В конструкторе вы можете запустить поток, который несколько раз вызывает originalThread.stop (ChuckNorisException.this)

Поток мог бы перехватить исключение несколько раз, но продолжал бы выбрасывать его, пока он не умрет.

Питер Лори
источник
13

Нет. Все исключения в Java должны быть подклассами java.lang.Throwable, и, хотя это может не быть хорошей практикой, вы можете перехватить каждый тип исключения следующим образом:

try {
    //Stuff
} catch ( Throwable T ){
    //Doesn't matter what it was, I caught it.
}

Увидеть java.lang.Throwable Документацию для получения дополнительной информации.

Если вы пытаетесь избежать проверенных исключений (тех, которые должны быть явно обработаны), тогда вы захотите создать подкласс Error или RuntimeException.

VolatileDream
источник
9

На самом деле принятый ответ не так хорош, потому что Java нужно запускать без проверки, то есть код не будет работать в нормальных условиях.

AspectJ на помощь для реального решения !

Исключительный класс:

package de.scrum_master.app;

public class ChuckNorrisException extends RuntimeException {
    public ChuckNorrisException(String message) {
        super(message);
    }
}

аспект:

package de.scrum_master.aspect;

import de.scrum_master.app.ChuckNorrisException;

public aspect ChuckNorrisAspect {
    before(ChuckNorrisException chuck) : handler(*) && args(chuck) {
        System.out.println("Somebody is trying to catch Chuck Norris - LOL!");
        throw chuck;
    }
}

Пример приложения:

package de.scrum_master.app;

public class Application {
    public static void main(String[] args) {
        catchAllMethod();
    }

    private static void catchAllMethod() {
        try {
            exceptionThrowingMethod();
        }
        catch (Throwable t) {
            System.out.println("Gotcha, " + t.getClass().getSimpleName() + "!");
        }
    }

    private static void exceptionThrowingMethod() {
        throw new ChuckNorrisException("Catch me if you can!");
    }
}

Вывод:

Somebody is trying to catch Chuck Norris - LOL!
Exception in thread "main" de.scrum_master.app.ChuckNorrisException: Catch me if you can!
    at de.scrum_master.app.Application.exceptionThrowingMethod(Application.java:18)
    at de.scrum_master.app.Application.catchAllMethod(Application.java:10)
    at de.scrum_master.app.Application.main(Application.java:5)
kriegaex
источник
8

Вариант на тему - удивительный факт, что вы можете бросить необъявленные проверенные исключения из кода Java. Поскольку оно не объявлено в сигнатуре методов, компилятор не позволит вам перехватить само исключение, хотя вы можете перехватить его как java.lang.Exception.

Вот вспомогательный класс, который позволяет вам бросать что-либо, объявленное или нет:

public class SneakyThrow {
  public static RuntimeException sneak(Throwable t) {
    throw SneakyThrow.<RuntimeException> throwGivenThrowable(t);
  }

  private static <T extends Throwable> RuntimeException throwGivenThrowable(Throwable t) throws T {
    throw (T) t;
  }
}

Теперь throw SneakyThrow.sneak(new ChuckNorrisException());выдает ChuckNorrisException, но компилятор жалуется

try {
  throw SneakyThrow.sneak(new ChuckNorrisException());
} catch (ChuckNorrisException e) {
}

о перехвате исключения, которое не выдается, если ChuckNorrisException является проверенным исключением.

Ханс-Петер Стёрр
источник
6

Единственные ChuckNorrisExceptions в Java должны быть OutOfMemoryErrorи StackOverflowError.

Вы действительно можете «поймать» их в том смысле, что catch(OutOfMemoryError ex) будет выполняться в случае исключения, но этот блок автоматически сбросит исключение для вызывающей стороны.

Я не думаю, что public class ChuckNorrisError extends Errorэто поможет, но вы могли бы попробовать. Я не нашел документации о расширенииError

USR-местный ΕΨΗΕΛΩΝ
источник
2
Ошибка по- прежнему распространяется Throwable, поэтому нет способа предотвратить ее перехват. Это дизайн языка Java.
JasonM1
1
@ JasonM1 Я не думаю, что OP запросил действительно «неуловимое» исключение, и я имел в виду, что ошибка распространяется, даже если вы ее поймаете. Таким образом, любой Throwable можно поймать, но эти два будут в конечном итоге распространяться независимо от того, что вы делаете
usr-local-ΕΨΗΕΛΩΝ
Если быть хитрым, ChuckNorrisException может расширять Throwable напрямую, тогда это не будет ни исключением, ни ошибкой!
JasonM1
4
Ошибка не распространяется, даже если вы ее поймете, я не уверен, откуда у вас эта идея.
Jtahlborn
3
Я думаю, что вы очень запутались в Erros, это обычные исключения, такие как все, что расширяет Throwable или даже Throwable, само по себе.
bestsss
6

Is it possible to construct a snippet of code in java that would make a hypothetical java.lang.ChuckNorrisException uncatchable?

Да, и вот ответ: спроектируйте так java.lang.ChuckNorrisException, чтобы это не было примером java.lang.Throwable. Почему? Невыбираемый объект по определению неуловим, потому что вы никогда не сможете поймать то, что никогда не бросите.

Томас Эдинг
источник
2
Но тогда это не исключение.
Долби
8
@dolbi: Я не могу найти место в вопросе ФП о том, что состояния java.lang.ChuckNorrisExceptionдолжны быть исключением, не говоря уже о бросаемом
Томас Эдинг
1
Я предполагаю, что это не указано, но это подразумевается. Вы математик :-), не так ли?
Долби
3

Вы можете оставить Чака Норриса внутренним или частным и заключить его в капсулу или проглотить его ...

try { doChuckAction(); } catch(ChuckNorrisException cne) { /*do something else*/ }

сойка
источник
7
Я не верю, что идея была поймать это. Я думаю, что идея состоит в том, чтобы предотвратить его отлов.
Патрик Робертс
Поправьте меня, если я ошибаюсь, но если вы сделаете это внутренним, вы не сможете добраться до этого без размышлений.
Джей
5
да, но до тех пор, пока вы можете поймать Exception или Throwable, видимость реального типа не имеет значения.
KeithS
3

Две фундаментальные проблемы с обработкой исключений в Java заключаются в том, что в ней используется тип исключения, чтобы указать, следует ли предпринимать действия на его основе, и что предполагается, что все, что предпринимает действие на основе исключения (то есть «ловит» его), разрешает основное условие. Было бы полезно иметь средство, с помощью которого объект исключения мог бы решить, какие обработчики должны выполняться, и выполнили ли обработчики, которые выполнялись до сих пор, достаточно вещей, чтобы существующий метод удовлетворял условиям его выхода. Хотя это может быть использовано для создания «неуловимых» исключений, два более масштабных применения будут заключаться в том, чтобы (1) создавать исключения, которые будут рассматриваться как обработанные, только когда они обнаружены кодом, который фактически знает, как с ними работать,finallyFooExceptionfinally блока при разматывании a BarExceptionоба исключения должны распространяться вверх по стеку вызовов; оба должны быть подловлены, но раскручивание должно продолжаться до тех пор, пока оба не будут пойманы). К сожалению, я не думаю, что был бы какой-то способ заставить существующий код обработки исключений работать таким образом, не ломая вещи.

Supercat
источник
интересная идея, но я не думаю, что низкоуровневый код будет знать, что конкретное исключение «означает» для вызывающей стороны, поэтому я не думаю, что для метателя будет когда-либо иметь смысл решать, какие обработчики должны выполняться.
Jtahlborn
@jtahlborn: прямо сейчас, метатель решает, какие обработчики исключений должны выполняться, выбирая тип исключения. Это делает практически невозможным чистую обработку некоторых сценариев. Среди прочего: (1) если исключение возникает во время finallyочистки блока от более раннего исключения, вполне возможно, что любое исключение, в отсутствие другого, может быть чем-то, что код будет обрабатывать и продолжать, но что обращаться с одним и игнорировать другое было бы плохо. Однако нет механизма для создания составного исключения, которое будут обрабатывать оба обработчика.
суперкат
@jtahlborn: Кроме того, из-за этого очень сложно разрешить обработку исключений, возникающих в обратных вызовах, внешним прикладным уровнем. Если исключение обратного вызова заключено в другой тип исключения, тип исключения обратного вызова не может использоваться на внешнем уровне при принятии решения о его перехвате; если оно не упаковано, «случайное» исключение среднего уровня может быть ошибочно принято за исключение, возникающее при обратном вызове. Если обернутому объекту исключения сообщили, когда он был передан внешнему прикладному уровню, он мог тогда начать отвечать на типы обернутых исключений.
суперкат
я не спорил с другими вашими соображениями, просто с утверждением об объекте исключения, решающим, какие обработчики будут выполняться. в некоторой степени типы исключений уже делают это, но, похоже, вы хотели что-то более динамичное, с чем я не согласен. я думаю, что ваш главный аргумент (который вы как бы приближаетесь) состоит в том, чтобы собрать как можно больше информации в нижней части и позволить верхним слоям видеть и работать со всей этой информацией. в этом общем пункте я согласен с вами, однако дьявол кроется в деталях / реализации.
Jtahlborn
@jtahlborn: Мое намерение состояло не в том, чтобы виртуальные методы реализовывали что-то особенно «динамическое», а, по сути, говорило: «Есть ли условие указанного типа, которое должно быть выполнено». Одна вещь, которую я забыл упомянуть, это то, что должен быть способ, с помощью которого код, который вызывает вызовы, Fooможет различать исключение, которое Fooлибо выдает себя, либо намеренно хочет притвориться, что оно выбрасывает сам, от того, которое Fooне ожидалось, когда оно было вызывая какой-то другой метод. Это то, что понятие «проверенных» исключений должно быть «примерно».
суперкат
1

Легко можно смоделировать необработанное исключение в текущем потоке. Это вызовет обычное поведение необработанного исключения и, таким образом, выполнит работу семантически. Это, однако, не обязательно остановит выполнение текущего потока, так как никакое исключение фактически не выдается.

Throwable exception = /* ... */;
Thread currentThread = Thread.currentThread();
Thread.UncaughtExceptionHandler uncaughtExceptionHandler =
    currentThread.getUncaughtExceptionHandler();
uncaughtExceptionHandler.uncaughtException(currentThread, exception);
// May be reachable, depending on the uncaught exception handler.

Это действительно полезно в (очень редких) ситуациях, например, когда Errorтребуется правильная обработка, но метод вызывается из каркаса, перехватывающего (и отбрасывающего) любой Throwable.

ДСТ
источник
0

Вызовите System.exit (1) в finalizeи просто сгенерируйте копию исключения из всех других методов, чтобы программа завершилась.

Деми
источник