Jeter enfin une exception à l'intérieur


27

Les analyseurs de code statique comme Fortify "se plaignent" lorsqu'une exception peut être levée à l'intérieur d'un finallybloc, ce qui dit cela Using a throw statement inside a finally block breaks the logical progression through the try-catch-finally. Normalement, je suis d'accord avec cela. Mais récemment, je suis tombé sur ce code:

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} catch (...) {
     //exception handling
} finally {
     if (writer!= null) writer.close();  
}

Maintenant, si le writerne peut pas être fermé correctement, la writer.close()méthode lèvera une exception. Une exception doit être levée car (très probablement) le fichier n'a pas été enregistré après l'écriture.

Je pourrais déclarer une variable supplémentaire, la définir s'il y avait une erreur lors de la fermeture de la writeret lever une exception après le bloc finally. Mais ce code fonctionne bien et je ne sais pas s'il faut le changer ou non.

Quels sont les inconvénients de lever une exception à l'intérieur du finallybloc?


4
S'il s'agit de Java et que vous pouvez utiliser Java 7, vérifiez si les blocs ARM peuvent résoudre votre problème.
Landei

@Landei, cela le résout, mais malheureusement nous n'utilisons pas Java 7.
superM

Je dirais que le code que vous avez montré n'est pas "Utiliser une instruction throw dans un bloc finally" et en tant que tel, la progression logique est très bien.
Mike

@ Mike, j'ai utilisé le résumé standard que Fortify montre, mais directement ou indirectement, il y a enfin une exception à l'intérieur.
superM

Malheureusement, le bloc try-with-resources est également détecté par Fortify comme exception levée finalement à l'intérieur .. il est trop intelligent, putain .. toujours pas sûr de savoir comment le surmonter, il semblait que la raison d'essayer avec des ressources était d'assurer la fermeture du ressources enfin, et maintenant chacune de ces déclarations est signalée par Fortify comme une menace pour la sécurité ..
mmona

Réponses:


18

Fondamentalement, les finallyclauses sont là pour assurer la bonne libération d'une ressource. Cependant, si une exception est levée à l'intérieur du bloc finally, cette garantie disparaît. Pire, si votre bloc de code principal lève une exception, l'exception levée dans le finallybloc la masquera. Il semblera que l'erreur a été provoquée par l'appel à close, pas pour la vraie raison.

Certaines personnes suivent un modèle désagréable de gestionnaires d'exceptions imbriquées, avalant toutes les exceptions levées dans le finallybloc.

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} finally {
    if (writer!= null) {
        try {
            writer.close();
        } catch (...) {
        }
    }
}

Dans les anciennes versions de Java, vous pouvez «simplifier» ce code en encapsulant les ressources dans des classes qui effectuent ce nettoyage «sûr» pour vous. Un bon ami à moi crée une liste de types anonymes, chacun fournissant la logique de nettoyage de leurs ressources. Ensuite, son code boucle simplement sur la liste et appelle la méthode dispose dans le finallybloc.


1
+1 Même si je fais partie de ceux qui utilisent ce code désagréable. Mais pour ma justification, je dirai que je ne le fais pas toujours, uniquement lorsque l'exception n'est pas critique.
superM

2
Je me retrouve à le faire aussi. Parfois, la création de tout gestionnaire de ressources (anonyme ou non) nuit à l'objectif du code.
Travis Parks

+1 de moi aussi; vous avez dit exactement ce que j'allais faire mais en mieux. Il convient de noter que certaines des implémentations Stream en Java ne peuvent pas réellement lever d'exceptions sur close (), mais l'interface le déclare car certaines d'entre elles le font. Donc, dans certaines circonstances, vous pourriez ajouter un bloc catch qui ne sera jamais réellement nécessaire.
vaughandroid

1
Qu'y a-t-il de si méchant à utiliser un bloc try / catch dans un bloc finally? Je préfère faire cela plutôt que de gonfler tout mon code en devant envelopper toutes mes connexions et flux, etc. juste pour qu'ils puissent être fermés tranquillement - ce qui, en soi, introduit un nouveau problème de ne pas savoir si la fermeture d'une connexion ou d'un flux ( ou autre) provoque une exception - ce que vous aimeriez peut-être savoir ou faire quelque chose ...
ban-geoengineering

10

Ce que Travis Parks a dit est vrai que les exceptions dans le finallybloc consommeront toutes les valeurs de retour ou les exceptions des try...catchblocs.

Si vous utilisez Java 7, cependant, le problème peut être résolu en utilisant un bloc try-with-resources . Selon les documents, tant que votre ressource implémente java.lang.AutoCloseable(la plupart des écrivains / lecteurs de bibliothèque le font maintenant), le bloc try-with-resources le fermera pour vous. L'avantage supplémentaire ici est que toute exception qui se produit lors de sa fermeture sera supprimée, permettant à la valeur de retour ou à l'exception d'origine de passer.

De

FileWriter writer = null;
try {
  writer = new FileWriter("myFile.txt");
  writer.write("hello");
} catch(...) {
  // return/throw new exception
} finally {
  writer.close(); // an exception would consume the catch block's return/exception
}

À

try (FileWriter writer = new FileWriter("myFile.txt")) {
  writer.write("hello");
} catch(...) {
  // return/throw new exception, always gets returned even if writer fails to close
}

http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


1
C'est parfois utile. Mais dans le cas où j'ai réellement besoin de lever une exception lorsque le fichier ne peut pas être fermé, cette approche masque les problèmes.
superM

1
@superM En fait, try-with-resources ne cache pas d'exception close(). "toute exception qui se produit lors de sa fermeture sera supprimée, permettant à la valeur de retour ou à l'exception d'origine de passer" - ce n'est tout simplement pas vrai . Une exception de la close()méthode sera supprimée uniquement lorsqu'une autre exception sera levée du bloc try / catch. Donc, si le trybloc ne lève aucune exception, l'exception de la close()méthode sera levée (et la valeur de retour ne sera pas retournée). Il peut même être capturé dans le catchbloc actuel .
Ruslan Stelmachenko

0

Je pense que c'est quelque chose que vous devrez aborder au cas par cas. Dans certains cas, ce que l'analyseur dit est correct en ce que le code que vous avez n'est pas si bon et a besoin d'être repensé. Mais il peut y avoir d'autres cas où lancer ou même relancer pourrait être la meilleure chose. Ce n'est pas quelque chose que vous pouvez mandater.


0

Cela va être davantage une réponse conceptuelle à la raison pour laquelle ces avertissements peuvent exister même avec des approches d'essayer avec des ressources. Ce n'est malheureusement pas non plus le type de solution facile que vous espérez atteindre.

La récupération d'erreur ne peut pas échouer

finally modélise un flux de contrôle post-transaction qui est exécuté, que la transaction réussisse ou échoue.

Dans le cas d'échec, finallycapture la logique qui est exécutée au milieu de la récupération d'une erreur, avant qu'elle ne soit complètement récupérée (avant d'atteindre notre catchdestination).

Imaginez le problème conceptuel qu'il présente pour rencontrer une erreur au milieu de la récupération d'une erreur.

Imaginez un serveur de base de données où nous essayons de valider une transaction, et il échoue à mi-chemin (par exemple, le serveur a manqué de mémoire au milieu). Maintenant, le serveur veut revenir à la transaction comme si de rien n'était. Imaginez cependant qu'il rencontre encore une autre erreur dans le processus de retour en arrière. Maintenant, nous finissons par avoir une transaction à moitié engagée dans la base de données - l'atomicité et la nature indivisible de la transaction sont désormais rompues, et l'intégrité de la base de données sera désormais compromise.

Ce problème conceptuel existe dans tous les langages traitant des erreurs, que ce soit C avec propagation manuelle de code d'erreur, ou C ++ avec exceptions et destructeurs, ou Java avec exceptions et finally.

finally ne peut pas échouer dans les langages qui le fournissent de la même manière que les destructeurs ne peuvent pas échouer en C ++ dans le processus de rencontrer des exceptions.

La seule façon d'éviter ce problème conceptuel et difficile est de s'assurer que le processus d'annulation des transactions et de libération des ressources au milieu ne peut pas rencontrer une exception / erreur récursive.

Donc, la seule conception sûre ici est une conception qui writer.close()ne peut pas échouer. Il existe généralement des moyens dans la conception pour éviter les scénarios où de telles choses peuvent échouer au milieu de la récupération, ce qui le rend impossible.

C'est malheureusement le seul moyen - la récupération d'erreur ne peut pas échouer. Le moyen le plus simple de garantir cela est de rendre ces types de fonctions de "libération des ressources" et "d'effets secondaires inversés" incapables d'échouer. Ce n'est pas facile - une récupération correcte des erreurs est difficile et malheureusement difficile à tester. Mais le moyen d'y parvenir est de s'assurer que les fonctions qui "détruisent", "ferment", "arrêtent", "reculent", etc. ne peuvent pas rencontrer d'erreur externe dans le processus, car ces fonctions devront souvent être appelé au milieu de la récupération d'une erreur existante.

Exemple: enregistrement

Supposons que vous souhaitiez enregistrer des éléments dans un finallybloc. Cela va souvent être un énorme problème à moins que la journalisation ne puisse échouer . L'enregistrement peut presque certainement échouer, car il peut vouloir ajouter plus de données au fichier, et cela peut facilement trouver de nombreuses raisons d'échouer.

Donc, la solution ici est de faire en sorte que toute fonction de journalisation utilisée dans les finallyblocs ne puisse pas être lancée à l'appelant (elle pourrait échouer, mais elle ne le lancera pas). Comment pourrions-nous faire cela? Si votre langue permet de lancer dans le contexte de enfin à condition qu'il y ait un bloc try / catch imbriqué, ce serait une façon d'éviter de lancer à l'appelant en avalant des exceptions et en les transformant en codes d'erreur, par exemple la journalisation peut être effectuée dans un autre processus ou thread qui peut échouer séparément et en dehors d'un déroulement de pile de récupération d'erreur existant. Tant que vous pouvez communiquer avec ce processus sans la possibilité de rencontrer une erreur, cela serait également sans danger pour les exceptions, car le problème de sécurité n'existe que dans ce scénario si nous lançons récursivement à partir du même thread.

Dans ce cas, nous pouvons nous en sortir avec un échec de journalisation à condition qu'il ne lance pas, car ne pas se connecter et ne rien faire n'est pas la fin du monde (il n'y a pas de fuite de ressources ou de réduction des effets secondaires, par exemple).

Quoi qu'il en soit, je suis sûr que vous pouvez déjà commencer à imaginer à quel point il est incroyablement difficile de vraiment sécuriser un logiciel. Il n'est peut-être pas nécessaire de rechercher cela au maximum dans tous les logiciels sauf les plus critiques. Mais il vaut la peine de prendre note de la façon de garantir véritablement la sécurité des exceptions, car même les auteurs de bibliothèques très générales tentent souvent ici et détruisent toute la sécurité des exceptions de votre application en utilisant la bibliothèque.

SomeFileWriter

Si SomeFileWriterpeut jeter à l'intérieur close, alors je dirais qu'il est généralement incompatible avec la gestion des exceptions, sauf si vous n'essayez jamais de le fermer dans un contexte qui implique la récupération d'une exception existante. Si le code pour cela est hors de votre contrôle, nous pourrions être SOL mais il serait utile d'aviser les auteurs de ce problème flagrant de sécurité d'exception. Si c'est sous votre contrôle, ma principale recommandation est de veiller à ce que sa fermeture ne puisse pas échouer par tous les moyens nécessaires.

Imaginez si un système d'exploitation ne parvient pas à fermer un fichier. Désormais, tout programme qui tente de fermer un fichier à l'arrêt ne pourra pas s'arrêter . Que sommes-nous censés faire maintenant, simplement maintenir l'application ouverte et dans les limbes (probablement non), simplement divulguer la ressource de fichier et ignorer le problème (peut-être bien si ce n'est pas si critique)? La conception la plus sûre: faites en sorte qu'il soit impossible de ne pas fermer un fichier.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.