Lanciare un'eccezione dentro finalmente


27

Analizzatori di codice statici come Fortify "si lamentano" quando un'eccezione potrebbe essere lanciata all'interno di un finallyblocco, dicendo che Using a throw statement inside a finally block breaks the logical progression through the try-catch-finally. Normalmente sono d'accordo con questo. Ma recentemente ho trovato questo codice:

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

Ora, se writernon è possibile chiudere correttamente, il writer.close()metodo genererà un'eccezione. Dovrebbe essere generata un'eccezione perché (molto probabilmente) il file non è stato salvato dopo la scrittura.

Potrei dichiarare una variabile aggiuntiva, impostarla in caso di errore durante la chiusura writere generare un'eccezione dopo il blocco finally. Ma questo codice funziona bene e non sono sicuro se modificarlo o meno.

Quali sono gli svantaggi di lanciare un'eccezione all'interno del finallyblocco?


4
Se questo è Java e puoi usare Java 7, controlla se i blocchi ARM possono risolvere il tuo problema.
Landei

@Landei, questo lo risolve, ma sfortunatamente non stiamo usando Java 7.
superM

Direi che il codice che hai mostrato non è "Usare un'istruzione throw all'interno di un blocco finally" e come tale la progressione logica va bene.
Mike

@ Mike, ho usato il riassunto standard che mostra Fortify, ma direttamente o indirettamente c'è un'eccezione generata finalmente.
superM

Sfortunatamente, il blocco try-with-resources viene anche rilevato da Fortify come eccezione lanciata alla fine .. è troppo intelligente, accidenti .. ancora non sono sicuro di come superarlo, sembrava che la ragione per provare con le risorse fosse quella di assicurare la chiusura del risorse finalmente, e ora ciascuna di queste dichiarazioni è segnalata da Fortify come una minaccia alla sicurezza ..
mmona

Risposte:


18

Fondamentalmente, finallyesistono clausole per garantire il rilascio corretto di una risorsa. Tuttavia, se viene generata un'eccezione all'interno del blocco finally, tale garanzia scompare. Peggio ancora, se il blocco di codice principale genera un'eccezione, l'eccezione sollevata nel finallyblocco la nasconderà. Sembrerà che l'errore sia stato causato dalla chiamata close, non per la vera ragione.

Alcune persone seguono una brutta sequenza di gestori di eccezioni nidificati, ingoiando qualsiasi eccezione generata nel finallyblocco.

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

Nelle versioni precedenti di Java, è possibile "semplificare" questo codice avvolgendo le risorse in classi che eseguono questa pulizia "sicura" per te. Un mio caro amico crea un elenco di tipi anonimi, ognuno dei quali fornisce la logica per ripulire le proprie risorse. Quindi il suo codice scorre semplicemente sull'elenco e chiama il metodo dispose all'interno del finallyblocco.


1
+1 Anche se sono tra quelli che usano quel brutto codice. Ma per mia giustificazione dirò che non lo faccio sempre, solo quando l'eccezione non è critica.
superM

2
Mi ritrovo a farlo anch'io. Talvolta la creazione di un intero gestore delle risorse (anonimo o no) pregiudica lo scopo del codice.
Travis Parks

+1 anche da me; in pratica hai detto esattamente quello che stavo per fare ma meglio. Vale la pena notare che alcune delle implementazioni di Stream in Java non possono effettivamente generare eccezioni su close (), ma l'interfaccia lo dichiara perché alcune lo fanno. Quindi, in alcune circostanze potresti aggiungere un blocco catch che non sarà mai effettivamente necessario.
vaughandroid

1
Cosa c'è di così brutto nell'usare un blocco try / catch all'interno di un blocco finally? Preferirei farlo piuttosto che gonfiare tutto il mio codice dovendo racchiudere tutte le mie connessioni e flussi, ecc. Solo in modo che possano essere chiusi in modo silenzioso, il che, di per sé, introduce un nuovo problema di non sapere se chiudere una connessione o un flusso ( o qualsiasi altra cosa) provoca un'eccezione - che è qualcosa che potresti davvero conoscere o fare qualcosa su ...
ban-geoengineering

10

Ciò che Travis Parks ha affermato è vero che le eccezioni nel finallyblocco consumeranno qualsiasi valore di ritorno o eccezioni dai try...catchblocchi.

Se si utilizza Java 7, tuttavia, il problema può essere risolto utilizzando un blocco try-with-resources . Secondo i documenti, fintanto che la tua risorsa viene implementata java.lang.AutoCloseable(la maggior parte degli scrittori / lettori di biblioteche lo fanno ora), il blocco try-with-resources lo chiuderà per te. Il vantaggio aggiuntivo qui è che qualsiasi eccezione che si verifica durante la chiusura verrà soppressa, consentendo il passaggio del valore di ritorno originale o dell'eccezione.

A partire dal

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
}

A

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
Questo è utile a volte. Ma nel caso in cui ho effettivamente bisogno di lanciare un'eccezione quando il file non può essere chiuso, questo approccio nasconde i problemi.
superM

1
@superM In realtà, try-with-resources non nasconde un'eccezione da close(). "qualsiasi eccezione che si verifica durante la chiusura verrà soppressa, consentendo il passaggio del valore di ritorno originale o dell'eccezione" - semplicemente non è vero . Un'eccezione dal close()metodo verrà soppressa solo quando un'altra eccezione verrà generata dal blocco try / catch. Pertanto, se il tryblocco non genera alcuna eccezione, close()verrà generata l'eccezione dal metodo (e il valore restituito non verrà restituito). Può anche essere catturato dal catchblocco corrente .
Ruslan Stelmachenko,

0

Penso che questo sia qualcosa che dovrai affrontare caso per caso. In alcuni casi ciò che l'analizzatore sta dicendo è corretto in quanto il codice che hai non è eccezionale e necessita di un ripensamento. Ma potrebbero esserci altri casi in cui lanciare o anche ri-lanciare potrebbe essere la cosa migliore. Non è qualcosa che puoi incaricare.


0

Questa sarà una risposta più concettuale al perché questi avvisi potrebbero esistere anche con approcci di prova con le risorse. Sfortunatamente non è anche il tipo semplice di soluzione che potresti sperare di ottenere.

Errore Il recupero non può fallire

finally modella un flusso di controllo post-transazione che viene eseguito indipendentemente dal fatto che una transazione abbia esito positivo o negativo.

In caso di fallimento, finallyacquisisce la logica che viene eseguita nel mezzo del recupero da un errore, prima che sia stata completamente ripristinata (prima che raggiungiamo la nostra catchdestinazione).

Immagina il problema concettuale che presenta di incontrare un errore nel mezzo del recupero da un errore.

Immagina un server di database in cui stiamo tentando di eseguire il commit di una transazione e non riesce a metà strada (ad esempio il server ha esaurito la memoria nel mezzo). Ora il server vuole ripristinare la transazione fino a un certo punto come se nulla fosse successo. Eppure, immagina di incontrare ancora un altro errore nel processo di rollback. Ora finiamo per avere una transazione parzialmente impegnata nel database: l'atomicità e la natura indivisibile della transazione sono ora interrotte e l'integrità del database sarà ora compromessa.

Questo problema concettuale esiste in qualsiasi linguaggio che tratta degli errori, sia che si tratti di C con propagazione manuale del codice di errore, o C ++ con eccezioni e distruttori, o Java con eccezioni e finally.

finally non può fallire nei linguaggi che lo forniscono allo stesso modo in cui i distruttori non possono fallire in C ++ nel processo di incontrare eccezioni.

L'unico modo per evitare questo problema concettuale e difficile è assicurarsi che il processo di rollback delle transazioni e il rilascio di risorse nel mezzo non possa eventualmente incontrare un'eccezione / errore ricorsivo.

Quindi l'unico design sicuro qui è un design in cui writer.close()non può assolutamente fallire. Di solito ci sono modi nella progettazione per evitare scenari in cui tali cose possono fallire nel mezzo del recupero, rendendolo impossibile.

Purtroppo è l'unico modo: il recupero degli errori non può fallire. Il modo più semplice per garantire ciò è rendere incapaci di fallire quei tipi di "rilascio di risorse" e "effetti collaterali". Non è facile: il corretto recupero degli errori è difficile e purtroppo anche difficile da testare. Ma il modo per ottenerlo è assicurarsi che eventuali funzioni che "distruggono", "chiudi", "arresta", "ripristina", ecc. Non possano eventualmente riscontrare un errore esterno nel processo, poiché tali funzioni dovranno spesso essere chiamato durante il recupero da un errore esistente.

Esempio: registrazione

Supponiamo che tu voglia registrare cose all'interno di un finallyblocco. Questo sarà spesso un grosso problema a meno che la registrazione non possa fallire . La registrazione quasi certamente può fallire, dal momento che potrebbe voler aggiungere più dati al file e che può facilmente trovare molti motivi per fallire.

Quindi la soluzione qui è di renderlo tale che qualsiasi funzione di registrazione utilizzata nei finallyblocchi non possa essere lanciata al chiamante (potrebbe non riuscire, ma non verrà lanciata). Come potremmo farlo? Se la tua lingua consente di lanciare nel contesto di finalmente a condizione che esista un blocco try / catch nidificato, sarebbe un modo per evitare di lanciare il chiamante ingoiando le eccezioni e trasformandole in codici di errore, ad esempio forse la registrazione può essere eseguita in un altro processo o thread che possono fallire separatamente e al di fuori di uno stack di recupero errori esistente svolgersi. Finché puoi comunicare con quel processo senza la possibilità di incorrere in un errore, sarebbe anche sicuro dalle eccezioni, poiché il problema di sicurezza esiste solo in questo scenario se lanciamo ricorsivamente all'interno dello stesso thread.

In questo caso, possiamo farcela con il fallimento della registrazione a condizione che non si getti poiché non riesce a fare il login e non fare nulla non è la fine del mondo (non perde risorse o non riesce a ripristinare gli effetti collaterali, ad esempio).

Ad ogni modo, sono sicuro che puoi già iniziare a immaginare quanto sia incredibilmente difficile rendere un software davvero sicuro. Potrebbe non essere necessario cercarlo al massimo in tutti tranne che nel software più critico. Ma vale la pena prendere nota di come raggiungere veramente la sicurezza delle eccezioni, poiché anche gli autori di librerie per scopi molto generici spesso armeggiano qui e rovinano l'intera sicurezza delle eccezioni dell'applicazione usando la libreria.

SomeFileWriter

Se SomeFileWriterriesco a lanciarci dentro close, direi che è generalmente incompatibile con la gestione delle eccezioni, a meno che tu non provi mai a chiuderlo in un contesto che comporta il recupero da un'eccezione esistente. Se il codice è al di fuori del tuo controllo, potremmo essere SOL, ma varrebbe la pena avvisare gli autori di questo palese problema di sicurezza delle eccezioni. Se è sotto il tuo controllo, la mia principale raccomandazione è di assicurarsi che la sua chiusura non possa fallire con qualsiasi mezzo necessario.

Immagina se un sistema operativo potrebbe effettivamente non riuscire a chiudere un file. Ora qualsiasi programma che tenta di chiudere un file all'arresto non si chiuderà . Cosa dovremmo fare ora, basta tenere aperta l'applicazione e nel limbo (probabilmente no), perdere la risorsa del file e ignorare il problema (forse va bene se non è così critico)? Il design più sicuro: rendilo così impossibile non riuscire a chiudere un file.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.