无法捕获的ChuckNorrisException


596

是否有可能在Java中构建一小段代码,以使其无法进行假设的java.lang.ChuckNorrisException捕获?

想到的想法正在使用例如拦截器或面向方面的编程



2
使用提供的@jschoen链接的建议(禁用字节码验证程序),您可以抛出不会扩展Throwable的内容。在下面的答案中有描述。
jtahlborn 2012年

4
来自aioobe答案的摘录总结了@jschoen链接得很好的问题:“即,您的问题可以解释为'如果JVM偏离规范,它可以做一些奇怪的事情,例如抛出primitivs',答案当然是,是。”
Dan在火光中摆弄

2
@Max-您可以详细说明这一点的实际用途吗?
Vineet Bhatia

3
重新抛出自身的异常怎么样finalize()
Lie Ryan

Answers:


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.

注释掉“扩展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"

18
好的,如果您抓住Object而不是Throwable,该怎么办?(编译器不允许这样做,但是由于我们已经禁用了验证程序,所以也许有人可以破解该字节码来进行验证。)
Ilmari Karonen 2012年

11
根据Java中的“您可以抛出什么”一文,您仍然可以捕获不扩展throwable的事物,但是抛出和捕获它们是未定义的行为。
VolatileDream

8
@dzieciou他们可以在一起是真实的。您可能可以在处理器类型上使用特定于操作系统的特定版本的Java环境来捕获它们。但是,如果标准中未指定是否可以捕获它,则称为未定义行为,因为Java的其他实现可能会选择使其不可捕获。
heinrich5991 2012年

2
m 我希望在176次投票中,您编写了一些JNI代码,可以对整个调用堆栈进行猴子修补,以重新抛出您的异常(当然是由ctor调用)。
kdgregory 2013年

3
当做完所有这些时,站着一条腿,拍拍自己的头,在吹口哨的同时搓揉肚子也是一个好主意...;);)
Glen Best,

120

在深思熟虑之后,我成功创建了一个不可捕获的异常。JulesWinnfield但是,我选择了它的名字,而不是Chuck,因为它是蘑菇云铺设的母亲例外。此外,它可能与您的想法不完全相同,但是肯定无法抓住它。观察:

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)
    {

    }
}

导致堆栈溢出-同样,未捕获异常。


32
+1用于在答案中使用堆栈溢出。开个玩笑,真是个好答案。
约西亚

7
适当的“不可捕获的异常”将确保所有封闭的finally块将在没有任何中间捕获的情况下执行。杀死系统不会引发异常-只会杀死系统。
supercat 2012年

4
您如何“投掷” JulesWinfield?系统在抛出之前是否会突然停止?
supercat 2012年

6
@mikeTheLiar:系统在构造函数期间退出,不是吗?该语句throw new Whatever()实际上分为两部分:Whatever it = new Whatever(); throw it;,并且系统在到达第二部分之前就死了。
supercat 2012年

5
@mikeTheLiar实际上可以轻松捕获Jules或Vincent ...如果设法将其抛出。创建您无法抛出的异常很容易:class cn extends exception{private cn(){}}
John Dvorak

85

有了这样的异常,显然必须使用System.exit(Integer.MIN_VALUE);构造函数中的a ,因为如果您抛出这样的异常,将会发生这种情况;)


32
+1; IMO这是唯一可能的解决方案。无法捕获的异常应终止程序...
home

7
不,当您抛出此类异常时,不会发生这种情况。未捕获的异常将终止单个线程,它不会退出jvm,在某些情况下System.exit本身甚至会引发SecurityException-并非每段代码都允许关闭程序。
josefx

3
您可以使用while(true){}代替System.exit()
Piotr Praszmo

2
实际上,您可以防止System.exit()从通过安装其不允许其安全管理工作。这会将构造函数转换为其他异常(SecurityException),该异常可能会被捕获。
jtahlborn 2012年

5
嗯,从技术上讲,您永远不会抛出异常。您甚至还没有构造要抛出的对象!
Thomas Eding


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


2
嗯,catch(Throwable t)调用任何方法或以其他方式使对象变异?可能导致catch子句进一步引发异常,从而使其不可能。
科尔顿2012年

1
我认为catch(Throwable t)只能将其存储到变量中,因此我的建议仅在用户要应对异常
时才

24

我的答案基于@jtahlborn的想法,但这是一个完全正常工作的Java程序,可以打包到JAR文件中,甚至可以作为Web应用程序的一部分部署到您喜欢的应用程序服务器中。

首先,让我们定义ChuckNorrisException类,以便它从一开始就不会崩溃JVM(Chuck确实很喜欢崩溃的JVM BTW :)

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

毫不奇怪-这毕竟是回旋踢:)


非常好!我自己在类定义操作方面做得并不多。您仍然需要在命令行上使用“ verify:none”吗?
jtahlborn 2012年

@jtahlborn是的,如果没有“ verify:none”,尝试抛出不是Throwable的后代的对象将失败。
Wildfire

哦,我的印象是,它以某种方式摆脱了这种限制。那么这与我的答案有何不同?
jtahlborn 2012年

2
主要的区别在于,它的无编译时使用的Java代码的黑客
野火

15

在构造函数中,您可以启动一个反复调用的线程 originalThread.stop (ChuckNorisException.this)

线程可以重复捕获该异常,但是会一直抛出该异常直到死亡。


13

不能。Java中的所有异常都必须是subclass java.lang.Throwable,尽管这可能不是一个好习惯,但是您可以捕获每种类型的异常,如下所示:

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

有关更多信息,请参见java.lang.Throwable文档。

如果您试图避免检查异常(必须明确处理的异常),则需要将Error或RuntimeException子类化。


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)

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

ChuckNorrisExceptionJava中唯一的s应该是OutOfMemoryErrorand StackOverflowError

实际上,您可以“捕获”它们的方式是catch(OutOfMemoryError ex),如果抛出异常,则a 将执行,但是该块会自动将异常重新引发给调用者。

我不认为这public class ChuckNorrisError extends Error可以解决问题,但是您可以尝试一下。我没有找到有关扩展的文档Error


2
错误仍然会扩展Throwable,因此无法阻止它。那是通过Java语言设计的。
JasonM1 2012年

1
@ JasonM1我不认为OP请求一个实际上“无法捕获的”异常,并且我的意思是即使您捕获了它,错误也会传播。因此,任何的Throwable是开捕但是这两个最终会传播,不管你做什么
USR-本地ΕΨΗΕΛΩΝ

要棘手的是,ChuckNorrisException可以直接扩展Throwable,那么它将既不是Exception也不是Error!
JasonM1 2012年

4
即使发现错误,错误也不会传播,我不确定您从何处得到这个想法。
jtahlborn 2012年

3
我认为您对Erros感到很困惑,它们是正常的例外,例如可扩展Throwable甚至Throwable本身的一切。
bestsss 2012年

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
但这也不例外。
dolbi 2012年

8
@dolbi:我能找到在OP的问题,没有地方,各国java.lang.ChuckNorrisException必须是一个例外,更不用说抛出
托马斯Eding

1
我想它没有说出来,但是暗示了。您是数学家:-),不是吗?
dolbi

3

您可以将ChuckNorris保留在内部或私人位置,并封装他或使他膨胀...

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


7
我不认为这个主意是要抓住它。我认为此举是为了防止被抓住。
Patrick Roberts

如果我错了,请纠正我,但是如果您将其内部化,那么您将无法反思。
周杰伦

5
是的,但是只要您可以捕获Exception或Throwable,实际类型的可见性就无关紧要。
KeithS 2012年

3

Java中异常处理的两个基本问题是,它使用异常的类型来指示是否应基于该异常采取措施,并且假定任何基于异常进行操作(即“捕获”它)的事物都可以解决基本条件。拥有一种方法,异常对象可以通过该方法来决定应执行哪些处理程序,以及到目前为止已执行的处理程序是否已对本方法进行了充分的整理以使其满足本方法的退出条件,这将非常有用。尽管这可以用来产生“无法捕获的”异常,但还有两个更大的用途是(1)产生异常,只有当异常被真正知道如何处理它们的代码捕获时,才会被视为已处理,finallyFooException在a finally的展开期间的一个块中BarException,两个异常都应沿调用堆栈传播;两者都应该是可捕获的,但是放松应该持续到两个都被捕获为止)。不幸的是,我认为没有任何方法可以使现有的异常处理代码以这种方式工作而不会破坏事物。


这是一个有趣的想法,但是我不认为低级代码会知道调用者特定的异常是什么意思,所以我认为对于抛出者来说,决定执行哪个处理程序是没有道理的。
jtahlborn 2012年

@jtahlborn:现在,抛出器通过选择异常类型来决定应执行哪些异常处理程序。这使得几乎不可能干净地处理某些情况。除其他事项外:(1)如果在finally从较早的异常中清除某个块时发生了异常,则很可能在没有其他异常的情况下,任何一个异常都可能是代码应处理并继续的事情,但是处理一个而忽略另一个将是不好的。但是,没有机制可以产生两个处理程序都可以处理的复合异常。
supercat 2012年

@jtahlborn:而且,这使得很难在回调中发生的异常由外部应用程序层处理。如果回调的异常包装在另一种异常类型中,则在决定是否捕获它时,不能在外层使用回调异常的类型。如果未包装,则可能将“偶然的”中间层异常误认为是回调中发生的异常。如果告知包装的异常对象何时将其传递到外部应用程序层,则它可以开始回答包装的异常的类型。
supercat

我不是在争论您的其他观点,只是关于异常对象的声明,决定将在哪些处理程序上执行。在某种程度上,异常类型已经做到了,但是听起来您想要更动态的东西,我不同意。我认为您的主要论点(有点像是横盘整理)是在底部捕获尽可能多的信息,并让高层看到并使用所有这些信息。在这个大体上,我同意你的观点,但是细节/实现是魔鬼。
jtahlborn 2012年

@jtahlborn:我的意图不是让虚拟方法实现特别是“动态”的任何东西,而是本质上说“存在指示类型的条件,应对此采取行动”。不过,我忘了提及的一件事是,应该有一种方法Foo可以使调用的代码可以区分一个异常,该异常Foo要么是自身抛出的,要么是故意假装自己抛出的,Foo而不是原来预期的那样。调用其他方法。这就是“检查的”异常的概念应该是“关于”的。
supercat

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需要适当的处理时,但是从捕获(并丢弃)any的框架中调用该方法Throwable


0

在中调用System.exit(1)finalize,并从所​​有其他方法中抛出异常的副本,以便程序退出。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.