├── settings.gradle ├── src └── main │ └── java │ └── com │ └── zyxist │ ├── other │ └── FooException.java │ ├── Example4.java │ ├── Extra.java │ ├── Example3.java │ ├── Example2.java │ ├── Example0.java │ ├── Example1.java │ ├── Example5.java │ └── chuck │ ├── ChuckNorrisException.java │ └── RoundhouseKick.java └── README.md /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'chuck-norris-exception' 2 | -------------------------------------------------------------------------------- /src/main/java/com/zyxist/other/FooException.java: -------------------------------------------------------------------------------- 1 | package com.zyxist.other; 2 | 3 | public class FooException extends Exception { 4 | public FooException(String string) { 5 | super(string); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/zyxist/Example4.java: -------------------------------------------------------------------------------- 1 | package com.zyxist; 2 | 3 | import com.zyxist.chuck.ChuckNorrisException; 4 | 5 | public class Example4 { 6 | public static void main(String[] args) { 7 | try { 8 | throw new ChuckNorrisException(); 9 | } catch(Throwable thr) { 10 | Exception ex = (Exception) thr; 11 | System.err.println("I've caught "+ex.getMessage()); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/zyxist/Extra.java: -------------------------------------------------------------------------------- 1 | package com.zyxist; 2 | 3 | import com.zyxist.chuck.ChuckNorrisException; 4 | 5 | public class Extra { 6 | public static void main(String[] args) { 7 | try { 8 | throw new ChuckNorrisException(); 9 | } catch(Throwable thr) { 10 | ChuckNorrisException ex = (ChuckNorrisException) thr; 11 | System.err.println("I've caught "+ex.getMessage()); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/zyxist/Example3.java: -------------------------------------------------------------------------------- 1 | package com.zyxist; 2 | 3 | import com.zyxist.chuck.ChuckNorrisException; 4 | 5 | public class Example3 { 6 | public static void main(String[] args) { 7 | try { 8 | throw new ChuckNorrisException(); 9 | } catch(Throwable thr) { 10 | System.err.println("I've caught "+thr.getMessage()); 11 | } finally { 12 | System.out.println("Finalization."); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/zyxist/Example2.java: -------------------------------------------------------------------------------- 1 | package com.zyxist; 2 | 3 | import com.zyxist.chuck.ChuckNorrisException; 4 | 5 | public class Example2 { 6 | public static void main(String[] args) { 7 | try { 8 | throw new ChuckNorrisException(); 9 | } catch(Exception thr) { 10 | System.err.println("I've caught "+thr.getClass().getSimpleName()); 11 | } finally { 12 | System.out.println("Finalization."); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/zyxist/Example0.java: -------------------------------------------------------------------------------- 1 | package com.zyxist; 2 | 3 | import com.zyxist.other.FooException; 4 | 5 | /** 6 | * This is how regular exceptions work. 7 | */ 8 | public class Example0 { 9 | 10 | public static void main(String args[]) { 11 | try { 12 | throw new FooException("Die!"); 13 | } catch (FooException exception) { 14 | System.out.println("Caught exception: " + exception.getMessage()); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/zyxist/Example1.java: -------------------------------------------------------------------------------- 1 | package com.zyxist; 2 | 3 | import com.zyxist.chuck.ChuckNorrisException; 4 | 5 | public class Example1 { 6 | public static void main(String[] args) { 7 | try { 8 | throw new ChuckNorrisException(); 9 | } catch(ChuckNorrisException thr) { 10 | System.err.println("I've caught "+thr.getClass().getSimpleName()); 11 | } finally { 12 | System.out.println("Finalization."); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/zyxist/Example5.java: -------------------------------------------------------------------------------- 1 | package com.zyxist; 2 | 3 | import com.zyxist.chuck.ChuckNorrisException; 4 | 5 | public class Example5 { 6 | public static void main(String[] args) throws InterruptedException { 7 | try { 8 | throw new ChuckNorrisException(); 9 | } catch(Throwable thr) { 10 | } 11 | System.out.println("Buahahaha!"); 12 | Thread.sleep(10000); 13 | System.out.println("Still alive!"); 14 | Thread.sleep(10000); 15 | System.out.println("Still alive! #2"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ChuckNorrisException 2 | ==================== 3 | 4 | > Chuck Norris throws exceptions that can't be caught. 5 | 6 | > If you catch ChuckNorrisException, you'll probably die. 7 | 8 | This is an implementation of uncatchable exception for Java. 9 | 10 | Quick start 11 | ----------- 12 | 13 | ```java 14 | public class Example { 15 | public static void main(String[] args) { 16 | try { 17 | throw new ChuckNorrisException(); 18 | } catch(ChuckNorrisException thr) { 19 | System.err.println("I've caught "+thr.getClass().getSimpleName()); 20 | } finally { 21 | System.out.println("Finalization."); 22 | } 23 | } 24 | } 25 | ``` 26 | 27 | For any other exception, the result of this program would be obvious. However, with `ChuckNorrisException` we get roundhouse-kicked: 28 | 29 | ```shell 30 | Finalization. 31 | Exception in thread "main" com.zyxist.chuck.ChuckNorrisException: I'm uncatchable! 32 | at com.zyxist.Example.main(Example.java:8) 33 | ``` 34 | 35 | More examples can be found in the source code! 36 | 37 | How it works? 38 | ------------- 39 | 40 | There are several features of Java compiler and JVM used to make this work: 41 | 42 | * there may exist multiple classes with the same name, but they must be loaded by different classloaders, 43 | * bytecode generation allows us generating an evil class and predenting we are "nice", 44 | * certain methods, such as `getMessage()` and `printStackTrace()` can be overriden, 45 | * it is possible to alter the stacktrace of the exception with `setStacktrace()` method, thus hiding our manipulations, 46 | * shutdown hook is our last line of defense. 47 | 48 | Basically, the constructor of the exception uses **Javassist** to make a copy of another class, `RoundhouseKick`, changes its name to `ChuckNorrisException` and generates a classloader for it. After instantiating that class, it is something completely different from JVM perspective and our **catch** no longer matches it. With bytecode manipulation, we can also easily avoid `catch (Exception)`. We can't avoid being caught by `catch (Throwable)`, but with the rest of the tricks, we can escape it without problems, and still hide from being easily detected. 49 | 50 | More explanation (in Polish) can be found at my blog: [www.zyxist.com](https://www.zyxist.com/blog/chuck-norris-exception). 51 | 52 | License 53 | ------- 54 | 55 | The code is in public domain. But please, don't use it in production. 56 | -------------------------------------------------------------------------------- /src/main/java/com/zyxist/chuck/ChuckNorrisException.java: -------------------------------------------------------------------------------- 1 | package com.zyxist.chuck; 2 | 3 | import static com.zyxist.chuck.RoundhouseKick.throwAsUnchecked; 4 | import java.lang.reflect.Constructor; 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | import javassist.ClassPool; 8 | import javassist.CtClass; 9 | 10 | /** 11 | * Uncatchable exception. 12 | */ 13 | public class ChuckNorrisException extends Exception { 14 | public ChuckNorrisException() { 15 | Throwable uncatchable = null; 16 | try { 17 | Class hackedChuck = rewriteWithDifferentClassLoader(); 18 | uncatchable = instantiateHackedClass(hackedChuck); 19 | uncatchable.setStackTrace(this.getStackTrace()); 20 | callInstallShutdownHook(hackedChuck, uncatchable); 21 | } catch(Exception exception) { 22 | throw new RuntimeException("Chucking didn't work: " + exception.getMessage(), exception); 23 | } 24 | dontLetItLiveTooMuch(uncatchable); 25 | throwAsUnchecked(uncatchable); 26 | } 27 | 28 | private Class rewriteWithDifferentClassLoader() throws Exception { 29 | ClassLoader cl = new ClassLoader(){}; 30 | 31 | ClassPool pool = ClassPool.getDefault(); 32 | CtClass chckCl = pool.get(RoundhouseKick.class.getCanonicalName()); 33 | // change the class name to ChuckNorrisException ;) 34 | chckCl.setName(this.getClass().getCanonicalName()); 35 | return pool.toClass(chckCl, cl, Class.class.getProtectionDomain()); 36 | } 37 | 38 | private Throwable instantiateHackedClass(Class hackedChuck) throws Exception { 39 | Constructor ctor = hackedChuck.getDeclaredConstructor(String.class); 40 | ctor.setAccessible(true); 41 | return (Throwable) ctor.newInstance("I'm uncatchable!"); 42 | } 43 | 44 | private void callInstallShutdownHook(Class hackedChuck, Throwable uncatchable) throws Exception { 45 | Method shutdownHookInstaller = hackedChuck.getDeclaredMethod("installShutdownHook", Class.class); 46 | shutdownHookInstaller.setAccessible(true); 47 | shutdownHookInstaller.invoke(uncatchable, RoundhouseKick.ShutdownThread.class); 48 | } 49 | 50 | private void dontLetItLiveTooMuch(final Throwable uncatchable) { 51 | new Thread() { 52 | @Override 53 | public void run() { 54 | try { 55 | Thread.sleep(100); 56 | try { 57 | Field roundhouseKickedField = uncatchable.getClass().getDeclaredField("roundhouseKicked"); 58 | roundhouseKickedField.setAccessible(true); 59 | boolean value = (Boolean) roundhouseKickedField.get(uncatchable); 60 | if (!value) { 61 | System.exit(0); 62 | } 63 | } catch(NoSuchFieldException | SecurityException | IllegalAccessException ex) { 64 | System.err.println("Oh sheet"); 65 | } 66 | } catch(InterruptedException exception) { 67 | System.exit(0); 68 | } 69 | } 70 | }.start(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/zyxist/chuck/RoundhouseKick.java: -------------------------------------------------------------------------------- 1 | package com.zyxist.chuck; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.InvocationTargetException; 5 | 6 | /** 7 | * Why needed: I keep this logic in a separate class to avoid problems 8 | * with instantiating ShutdownThread, after messing with the class loaders. 9 | */ 10 | public class RoundhouseKick extends Throwable { // note that after conversion, we no longer extend 'Exception', avoiding 'catch(Exception)' clauses. 11 | private boolean roundhouseKicked = false; 12 | 13 | public RoundhouseKick(String msg) { 14 | super(msg); 15 | } 16 | 17 | // The methods below work like the original, but only in the uncaught exception 18 | // handler. In any other context, they rethrow our exception back. This is useful 19 | // for escaping the 'catch(Throwable)' construct, which we can't avoid. 20 | 21 | @Override 22 | public String getMessage() { 23 | if (!isWithinUndeclaredThrowableHandler()) { 24 | throwAsUnchecked(this); 25 | } 26 | roundhouseKicked = true; 27 | return super.getMessage(); 28 | } 29 | 30 | @Override 31 | public StackTraceElement[] getStackTrace() { 32 | if (!isWithinUndeclaredThrowableHandler()) { 33 | throwAsUnchecked(this); 34 | } 35 | roundhouseKicked = true; 36 | return super.getStackTrace(); 37 | } 38 | 39 | @Override 40 | public void printStackTrace() { 41 | if (!isWithinUndeclaredThrowableHandler()) { 42 | throwAsUnchecked(this); 43 | } 44 | roundhouseKicked = true; 45 | super.printStackTrace(); 46 | } 47 | 48 | // To check if we are in the uncaught exception handler, we create a fake exception just 49 | // to extract the stacktrace from it and examine it. 50 | 51 | private boolean isWithinUndeclaredThrowableHandler() { 52 | RuntimeException inspector = new RuntimeException("Stacktrace inspector"); 53 | for (StackTraceElement element: inspector.getStackTrace()) { 54 | if (element.getClassName().equals("java.lang.Thread") && element.getMethodName().equals("dispatchUncaughtException")) { 55 | return true; 56 | } 57 | } 58 | return false; 59 | } 60 | 61 | // Java Virtual Machine does not know the concept of 'checked' exceptions. They are just a compile-time 62 | // check. With a little help of generics, we can trick the Java compiler and force it to generate a code 63 | // that throws checked exception as an unchecked exception thanks to the type erasure. 64 | 65 | static void throwAsUnchecked(Throwable exception) { 66 | RoundhouseKick.throwChuckedException(exception); 67 | } 68 | 69 | @SuppressWarnings("unchecked") 70 | private static void throwChuckedException(Throwable e) throws E { 71 | throw (E) e; 72 | } 73 | 74 | // Last line of defense - the programmer used 'catch(Throwable)' and did nothing inside it. 75 | // To escape from this situation, we do two things: 76 | // 1. start a thread that kills JVM after 100 ms (penalty for ignoring exceptions) 77 | // 2. install a shutdown hook that re-throws our original exception (part of this code is in ChuckNorrisException). 78 | // Because of the classloader tricks, we must do everything through the reflection to avoid 79 | // compilation problems. 80 | // 81 | // The 'roundhouseKicked' flag is a signal for us that our exception has been handled and we don't have to 82 | // kill JVM. 83 | 84 | private void installShutdownHook(Class shutdownHookClass) { 85 | try { 86 | Thread thr = (Thread) shutdownHookClass.getConstructor(String.class, Throwable.class).newInstance(Thread.currentThread().getName(), this); 87 | Runtime.getRuntime().addShutdownHook(thr); 88 | } catch(NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | InvocationTargetException ex) { 89 | System.err.println("Oh cr44p"); 90 | } 91 | } 92 | 93 | public static class ShutdownThread extends Thread { 94 | private final Throwable hackedException; 95 | 96 | public ShutdownThread(String originalThreadName, Throwable hackedException) { 97 | super(originalThreadName); 98 | this.hackedException = hackedException; 99 | } 100 | 101 | @Override 102 | public void run() { 103 | try { 104 | Field roundhouseKickedField = hackedException.getClass().getDeclaredField("roundhouseKicked"); 105 | roundhouseKickedField.setAccessible(true); 106 | boolean value = (Boolean) roundhouseKickedField.get(hackedException); 107 | if (!value) { 108 | throwAsUnchecked(hackedException); 109 | } 110 | } catch(NoSuchFieldException | SecurityException | IllegalAccessException ex) { 111 | System.err.println("Oh sheet"); 112 | } 113 | } 114 | } 115 | } 116 | --------------------------------------------------------------------------------