Mandrin inaccessibleNorrisException

596

Est-il possible de construire un extrait de code en Java qui rendrait une hypothétique java.lang.ChuckNorrisExceptioninaccessible?

Les pensées qui me viennent à l'esprit utilisent par exemple des intercepteurs ou une programmation orientée aspect .

Max Charas
la source
2
en utilisant la suggestion du lien @jschoen fourni (désactivez le vérificateur de code d'octet), vous pouvez lancer quelque chose qui ne s'étend pas Throwable! décrit dans ma réponse ci-dessous.
jtahlborn
4
Cet extrait de la réponse d'Aioobe résume assez bien la question @jschoen liée: "C'est-à-dire que votre question peut être interprétée comme" si une machine virtuelle Java s'écarte de la spécification, peut-elle faire des trucs bizarres tels que lancer des primitives "et la réponse est bien sûr, Oui."
Dan est en train de jouer par Firelight
2
@Max - Pouvez-vous nous en dire plus sur les utilisations pratiques?
Vineet Bhatia
3
que diriez-vous d'une exception qui se renverse sur le finalize()?
Lie Ryan

Réponses:

314

Je n'ai pas essayé cela, donc je ne sais pas si la JVM restreindrait quelque chose comme ça, mais peut-être pourriez-vous compiler du code qui lance ChuckNorrisException, mais au moment de l'exécution, fournissez une définition de classe ChuckNorrisExceptionqui ne prolonge pas Throwable .

MISE À JOUR:

Ça ne marche pas. Il génère une erreur de vérificateur:

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.

MISE À JOUR 2:

En fait, vous pouvez le faire fonctionner si vous désactivez le vérificateur de code d'octet! ( -Xverify:none)

MISE À JOUR 3:

Pour ceux qui suivent de chez eux, voici le script complet:

Créez les classes suivantes:

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.");
        }
    }
}

Compiler les classes:

javac -cp . TestVillain.java ChuckNorrisException.java

Courir:

java -cp . TestVillain
Gotcha!
The end.

Commentez "étend RuntimeException" et recompilez ChuckNorrisException.javauniquement :

javac -cp . ChuckNorrisException.java

Courir:

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.

Exécuter sans vérification:

java -Xverify:none -cp . TestVillain
The end.
Exception in thread "main"
jtahlborn
la source
18
OK, alors si vous attrapez à la Objectplace de Throwable, alors? (Le compilateur ne le permettra pas, mais comme nous avons déjà désactivé le vérificateur, on pourrait peut-être pirater le bytecode pour le faire.)
Ilmari Karonen
11
Selon Que pouvez-vous lancer en Java, vous pouvez toujours attraper des choses qui ne s'étendent pas, mais les lancer et les attraper est un comportement indéfini.
VolatileDream
8
@dzieciou Ils peuvent être vrais ensemble. Vous pourrez peut-être les détecter en utilisant votre version de l'environnement Java sur votre version spécifique de votre système d'exploitation sur votre type de processeur. Mais s'il n'est pas spécifié dans la norme s'il PEUT être intercepté, cela s'appelle un comportement non défini, car d'autres implémentations de Java peuvent choisir de le rendre non capturable.
heinrich5991
2
Hmmph. J'espérais que pour 176 votes positifs, vous aviez écrit du code JNI qui patche la totalité de la pile d'appels pour renvoyer votre exception (appelée par le ctor, bien sûr).
kdgregory
3
En faisant tout cela, c'est aussi une excellente idée de se tenir sur une jambe, de se tapoter la tête et de se frotter le ventre tout en sifflant dixie ...;);)
Glen Best
120

Après avoir réfléchi à cela, j'ai réussi à créer une exception inaccessible. J'ai choisi de le nommer JulesWinnfield, cependant, plutôt que Chuck, parce que c'est une exception mère-champignon-pose-nuage. De plus, ce n'est peut-être pas exactement ce que vous aviez en tête, mais cela ne peut certainement pas être capturé. Observer:

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");
    }
}

Et voilà! Exception non interceptée.

Production:

courir:

Dites «Quoi» encore! Je te défie! Je double osez-vous!

Résultat Java: 8

CONSTRUIRE AVEC SUCCÈS (temps total: 0 seconde)

Quand j'aurai un peu plus de temps, je verrai si je ne peux pas trouver autre chose aussi.

Vérifiez également ceci:

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

    }
}

Provoque un débordement de pile - encore une fois, les exceptions restent non détectées.

MikeTheLiar
la source
32
+1 pour utiliser Stack Overflow dans votre réponse. Je plaisante, vraiment une bonne réponse.
Josiah
7
Une "exception non capturable" appropriée garantirait que tous les blocs englobant enfin s'exécuteraient sans aucune capture intermédiaire. Tuer le système ne lève pas d'exception - il tue simplement le système.
supercat
4
Comment "lancez-vous" le JulesWinfield? Le système ne s'arrêtera-t-il pas brutalement avant d'être jeté?
supercat
6
@mikeTheLiar: Le système se ferme pendant le constructeur, n'est-ce pas? L'instruction throw new Whatever()est en réalité en deux parties:, Whatever it = new Whatever(); throw it;et le système meurt avant d'atteindre la deuxième partie.
supercat
5
@mikeTheLiar vous pouvez réellement attraper Jules ou Vincent assez facilement ... si vous parvenez à le lancer. Il est facile de créer une exception que vous ne pouvez pas lancer:class cn extends exception{private cn(){}}
John Dvorak
85

Avec une telle exception, il serait évidemment obligatoire d'utiliser un System.exit(Integer.MIN_VALUE);du constructeur car c'est ce qui se passerait si vous leviez une telle exception;)

Korgen
la source
32
+1; OMI, c'est la seule solution possible. Une exception non capturable est de mettre fin au programme ...
home
7
Non, ce ne serait pas ce qui se passe lorsque vous lancez une telle exception. Une exception non capturée mettra fin à un seul thread, elle ne quittera pas le jvm, dans certains contextes, System.exit lui-même provoquera même une SecurityException - tous les morceaux de code ne sont pas autorisés à arrêter un programme.
josefx
3
Vous pouvez utiliser à la while(true){}place de System.exit().
Piotr Praszmo
2
en fait, vous pouvez empêcher System.exit()de travailler en installant un gestionnaire de sécurité qui l'interdit. cela transformerait le constructeur en une exception différente (SecurityException), qui pourrait être interceptée.
jtahlborn
5
Umm, techniquement, vous n'avez jamais levé d'exception. Vous n'avez même pas encore construit l'objet à lancer!
Thomas Eding
46

Tout code peut intercepter Throwable. Donc non, quelle que soit l'exception que vous créez, elle sera une sous-classe de Throwable et sera sujette à être interceptée.

Nathan Hughes
la source
11
Throwable se serait hanglui - même tenté d'attraper ChuckNorrisException: P
PermGenError
35
public class ChuckNorrisException extends Exception {
    public ChuckNorrisException() {
        System.exit(1);
    }
}

(Certes, techniquement, cette exception n'est jamais levée, mais un véritable ChuckNorrisExceptionne peut pas être levé - il vous lance en premier.)

duveteux
la source
4
Un de mes collègues avait suggéré de coller «for (;;) {}» car il pensait qu'un appel «System.exit (1)» pouvait lever une exception de sécurité. Je vote pour la créativité!
Phil Street
Je suis d'accord avec la fin de votre réponse. Ne jouez jamais avec ChuckNorris, Exception ou non.
Benj
28

Toute exception que vous lancez doit étendre Throwable, afin qu'elle puisse toujours être interceptée. La réponse est donc non.

Si vous voulez le rendre difficile à manipuler, vous pouvez remplacer les méthodes getCause(), getMessage(), getStackTrace(), toString()de jeter un autre java.lang.ChuckNorrisException.

mirelon
la source
2
Hmm, catch (Throwable t) appelle des méthodes ou mute l'objet autrement? Il peut être possible d'amener une clause catch à lever une exception à cet effet, la rendant impossible.
Colton
1
Je pense catch(Throwable t)que le stocke uniquement dans une variable, donc mes suggestions ne s'appliquent que dans le bloc suivant lorsque l'utilisateur veut faire face à l'exception
mirelon
24

Ma réponse est basée sur l'idée de @ jtahlborn, mais c'est un programme Java pleinement fonctionnel , qui peut être empaqueté dans un fichier JAR et même déployé sur votre serveur d'applications préféré dans le cadre d'une application Web .

Tout d'abord, définissons la ChuckNorrisExceptionclasse afin qu'elle ne plante pas la JVM depuis le début (Chuck aime vraiment planter les 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);
    }
}

Va maintenant en Expendablesclasse pour le construire:

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);
        }
    }
}

Et enfin la Mainclasse pour botter les fesses:

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");
        }
    }
}

Compilez-le et exécutez-le avec la commande suivante:

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

Vous obtiendrez la sortie suivante:

before
finally

Pas de surprise - c'est un coup de pied rond après tout :)

Incendies
la source
très agréable! n'ont pas fait grand-chose moi-même avec la manipulation des définitions de classe. avez-vous toujours besoin de la "vérification: aucun" sur la ligne de commande?
jtahlborn
@jtahlborn Oui, la tentative de lancer un objet non descendant de Throwable échoue sans "vérifier: aucun".
Wildfire
oh, j'ai l'impression que cela a en quelque sorte contourné cette contrainte. alors en quoi est-ce différent de ma réponse?
jtahlborn
2
La principale différence est qu'il fonctionne avec du code java sans piratage à la compilation
Wildfire
15

Dans le constructeur, vous pouvez démarrer un thread qui appelle à plusieurs reprises originalThread.stop (ChuckNorisException.this)

Le thread pourrait intercepter l'exception à plusieurs reprises mais continuerait à le lancer jusqu'à ce qu'il meure.

Peter Lawrey
la source
13

Non. Toutes les exceptions en Java doivent être de sous java.lang.Throwable- classe , et bien que ce ne soit pas une bonne pratique, vous pouvez intercepter tout type d'exception comme ceci:

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

Voir le java.lang.Throwable documentation pour plus d'informations.

Si vous essayez d'éviter les exceptions vérifiées (celles qui doivent être explicitement gérées), vous souhaiterez sous-classer Error ou RuntimeException.

VolatileDream
la source
9

En fait, la réponse acceptée n'est pas si agréable car Java doit être exécuté sans vérification, c'est-à-dire que le code ne fonctionnerait pas dans des circonstances normales.

AspectJ à la rescousse de la vraie solution !

Classe d'exception:

package de.scrum_master.app;

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

Aspect:

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;
    }
}

Exemple d'application:

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!");
    }
}

Production:

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
la source
8

Une variante du thème est le fait surprenant que vous pouvez lever des exceptions vérifiées non déclarées à partir du code Java. Comme il n'est pas déclaré dans la signature des méthodes, le compilateur ne vous permet pas d'attraper l'exception elle-même, bien que vous puissiez l'attraper en tant que java.lang.Exception.

Voici une classe d'aide qui vous permet de lancer n'importe quoi, déclaré ou non:

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());Lance maintenant une exception ChuckNorrisException, mais le compilateur se plaint dans

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

sur la capture d'une exception qui n'est pas levée si ChuckNorrisException est une exception vérifiée.

Hans-Peter Störr
la source
6

Les seuls ChuckNorrisExceptions en Java devraient être OutOfMemoryErroret StackOverflowError.

Vous pouvez réellement les "attraper" dans le sens où un catch(OutOfMemoryError ex) s'exécutera au cas où l'exception serait levée, mais ce bloc renverra automatiquement l'exception à l'appelant.

Je ne pense pas que cela public class ChuckNorrisError extends Errorfasse l'affaire, mais vous pouvez essayer. Je n'ai trouvé aucune documentation sur l'extensionError

usr-local-ΕΨΗΕΛΩΝ
la source
2
L'erreur étend toujours Throwable, donc aucun moyen d'empêcher sa capture. C'est par conception du langage Java.
JasonM1
1
@ JasonM1 Je ne pense pas que l'OP ait demandé une exception réellement "inaccessible", et je voulais dire que l'erreur se propage même si vous l'attrapez. Donc, tout Throwable est capturable mais ces deux-là finiront par se propager quoi que vous fassiez
usr-local-ΕΨΗΕΛΩΝ
Être délicat ChuckNorrisException pourrait étendre Throwable directement alors ce ne serait ni une exception ni une erreur!
JasonM1
4
L'erreur ne se propage pas même si vous l'attrapez, je ne sais pas d'où vous vient cette idée.
jtahlborn
3
Je pense que vous êtes bien confus à propos d'Erros, ce sont des exceptions normales comme tout ce qui étend Throwable ou même Throwable, lui-même.
bestsss
6

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

Oui, et voici la réponse: Concevez votre java.lang.ChuckNorrisExceptiontel qu'il ne soit pas une instance de java.lang.Throwable. Pourquoi? Un objet non déployable est par définition inaccessible car vous ne pouvez jamais attraper quelque chose qui ne peut jamais être lancé.

Thomas Eding
la source
2
Mais ce n'est pas une exception.
dolbi
8
@dolbi: Je ne trouve aucune place dans la question du PO selon laquelle les états java.lang.ChuckNorrisExceptiondoivent être une exception, et encore moins jetables
Thomas Eding
1
Je suppose que ce n'est pas indiqué, mais c'est implicite. Vous êtes mathématicien :-), n'est-ce pas?
dolbi
3

Vous pouvez garder ChuckNorris interne ou privé et l'encapsuler ou le faire glisser ...

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

Geai
la source
7
Je ne crois pas que l'idée était de l'attraper. Je pense que l'idée est d' empêcher qu'il ne soit attrapé.
Patrick Roberts
Corrigez-moi si je me trompe, mais si vous le rendez interne, vous ne pouvez pas y accéder sans réflexion.
Jay
5
oui, mais tant que vous pouvez attraper Exception ou Throwable, la visibilité du type réel n'a pas d'importance.
KeithS
3

Deux problèmes fondamentaux avec la gestion des exceptions en Java sont qu'il utilise le type d'une exception pour indiquer si une action doit être prise en fonction de celle-ci, et que tout ce qui prend une action en fonction d'une exception (c'est-à-dire "l'attraper" est supposé résoudre la condition sous-jacente. Il serait utile d'avoir un moyen par lequel un objet d'exception pourrait décider quels gestionnaires devraient exécuter, et si les gestionnaires qui ont exécuté jusqu'à présent ont suffisamment nettoyé les choses pour que la méthode actuelle satisfasse ses conditions de sortie. Bien que cela puisse être utilisé pour créer des exceptions "inaccessibles", deux utilisations plus importantes consisteraient à (1) créer des exceptions qui ne seront considérées comme gérées que lorsqu'elles sont détectées par un code qui sait réellement comment les traiter,finallyFooExceptionpendant un finallybloc pendant le déroulement d'un BarException, les deux exceptions doivent se propager dans la pile des appels; les deux doivent être capturables, mais le déroulement doit se poursuivre jusqu'à ce que les deux soient capturés). Malheureusement, je ne pense pas qu'il y aurait moyen de faire fonctionner le code de gestion des exceptions de cette manière sans casser les choses.

supercat
la source
une idée intéressante, mais je ne pense pas que le code de bas niveau sache ce qu'une exception particulière "signifie" pour l'appelant, donc je ne pense pas que cela aurait du sens pour le lanceur de décider quels gestionnaires devraient exécuter.
jtahlborn
@jtahlborn: En ce moment, le lanceur décide quels gestionnaires d'exceptions doivent exécuter via le choix du type d'exception. Cela rend pratiquement impossible de gérer correctement certains scénarios. Entre autres choses: (1) si une exception se produit alors qu'un finallybloc se nettoie d'une exception antérieure, il est fort possible que l'une ou l'autre exception, en l'absence de l'autre, soit quelque chose que le code devrait gérer et continuer, mais que manipuler l'un et ignorer l'autre serait mauvais. Il n'y a cependant aucun mécanisme pour produire une exception composite que les deux gestionnaires traiteraient.
supercat
@jtahlborn: En outre, cela rend très difficile la gestion des exceptions se produisant dans les rappels par la couche d'application externe. Si l'exception du rappel est encapsulée dans un autre type d'exception, le type d'exception de rappel ne peut pas être utilisé dans la couche externe pour décider de l'attraper; s'il n'est pas encapsulé, une exception de couche intermédiaire "accidentelle" peut être confondue avec celle qui se produit dans le rappel. Si un objet exception encapsulé était informé de sa transmission à la couche d'application externe, il pourrait alors commencer à répondre aux types d'exceptions encapsulées.
supercat
Je ne discutais pas vos autres points, juste la déclaration sur l'objet d'exception qui décidait quels gestionnaires exécuteraient. dans une certaine mesure, les types d'exception le font déjà, mais il semble que vous vouliez quelque chose de plus dynamique, ce avec quoi j'étais en désaccord. Je pense que votre argument principal (que vous venez de côté) est de capturer autant d'informations que possible en bas et de laisser les couches supérieures voir et travailler avec toutes ces informations. sur ce point général, je suis d'accord avec vous, mais le diable est dans les détails / mise en œuvre.
jtahlborn
@jtahlborn: Mon intention n'était pas de faire en sorte que les méthodes virtuelles implémentent quelque chose de particulièrement "dynamique", mais de dire essentiellement "Y a-t-il une condition du type indiqué qui devrait être appliquée". Une chose que j'ai oublié de mentionner, cependant, est qu'il devrait y avoir un moyen par lequel le code qui appelle Foopeut distinguer une exception qui Foos'est levée ou veut délibérément prétendre qu'elle s'est jetée, de celle qui Foone s'attendait pas à se produire lorsqu'elle était appeler une autre méthode. Voilà à quoi devrait ressembler la notion d'exceptions «vérifiées».
supercat
1

Il est facilement possible de simuler une exception non interceptée sur le thread actuel. Cela déclenchera le comportement normal d'une exception non interceptée et fera donc le travail sémantiquement. Cependant, cela n'arrêtera pas nécessairement l'exécution du thread actuel, car aucune exception n'est réellement levée.

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

Ceci est en fait utile dans des situations (très rares), par exemple lorsqu'une Errormanipulation appropriée est requise, mais la méthode est invoquée à partir d'un framework capturant (et rejetant) any Throwable.

dst
la source
0

Appelez System.exit (1) dans le finalize, et jetez simplement une copie de l'exception de toutes les autres méthodes, afin que le programme se termine.

Demi
la source