Errore di codice irraggiungibile rispetto all’avviso di codice morto in Java sotto Eclipse?

Qualcuno sa perché:

public void foo() { System.out.println("Hello"); return; System.out.println("World!"); } 

Sarebbe segnalato come un “errore irraggiungibile” in Eclipse, ma

 public void foo() { System.out.println("Hello"); if(true) return; System.out.println("World!"); } 

Attiva solo un avviso “Codice Morto”?

L’unica spiegazione che posso pensare è che il compilatore Java contrassegna solo il primo e che un’ulteriore analisi in Eclipse calcoli il secondo. Tuttavia, se questo è il caso, perché il compilatore Java non può capire questo caso al momento della compilazione?

Il compilatore Java non capirebbe al momento della compilazione che if (true) non ha alcun effetto, producendo così bytecode che è essenzialmente identico? A che punto è applicata l’analisi del codice raggiungibile?

    Immagino che un modo più generale di pensare a questa domanda sia: “quando viene applicata l’analisi del codice raggiungibile”? Nella trasformazione del secondo frammento di codice Java nel bytecode finale, sono sicuro che ad un certo punto l’equivalente di runtime “if (true)” viene rimosso e le rappresentazioni dei due programmi diventano identiche. Il compilatore Java non applicherà di nuovo l’analisi del codice raggiungibile?

    Il primo non si compila (hai un errore), il secondo compila (hai appena ricevuto un avvertimento). Questa è la differenza.

    Per quanto riguarda il motivo per cui Eclipse rileva il codice morto, beh, questa è solo la comodità di uno strumento di sviluppo integrato con un compilatore integrato che può essere ottimizzato rispetto a JDK per rilevare questo tipo di codice.

    Aggiornamento : il JDK elimina effettivamente il codice morto.

     public class Test { public void foo() { System.out.println("foo"); if(true)return; System.out.println("foo"); } public void bar() { System.out.println("bar"); if(false)return; System.out.println("bar"); } } 

    javap -c dice:

     test di class pubblica estende java.lang.Object {
     test pubblico ();
       Codice:
        0: aload_0
        1: invokespecial # 1;  // Metodo java / lang / Object. "" :() V
        4: ritorno
    
     public void foo ();
       Codice:
        0: getstatic # 2;  // Field java / lang / System.out: Ljava / io / PrintStream;
        3: ldc # 3;  // String foo
        5: invokevirtual # 4;  // Metodo java / io / PrintStream.println: (Ljava / lang / StrV
        8: ritorno
    
     public void bar ();
       Codice:
        0: getstatic # 2;  // Field java / lang / System.out: Ljava / io / PrintStream;
        3: ldc # 5;  // Barra delle stringhe
        5: invokevirtual # 4;  // Metodo java / io / PrintStream.println: (Ljava / lang / String;) V
        8: getstatic # 2;  // Field java / lang / System.out: Ljava / io / PrintStream;
        11: ldc # 5;  // Barra delle stringhe
        13: invokevirtual # 4;  // Metodo java / io / PrintStream.println: (Ljava / lang / String;) V
        16: ritorno
    
     }
    

    Per quanto riguarda il motivo per cui (Sun) non fornisce un avvertimento al riguardo, non ne ho idea 🙂 Almeno il compilatore JDK ha in realtà un DCE (Dead Code Elimination) integrato.

    Il codice non raggiungibile è un errore in base alle specifiche del linguaggio Java .

    Per citare il JLS:

    L’idea è che ci deve essere un ansible percorso di esecuzione dall’inizio del costruttore, del metodo, dell’inizializzatore di istanze o dell’inizializzatore statico che contiene l’istruzione nell’istruzione stessa. L’analisi tiene conto della struttura delle affermazioni. Fatta eccezione per il trattamento speciale di while, do e per le istruzioni la cui espressione condizionale ha il valore costante true, i valori delle espressioni non vengono presi in considerazione nell’analisi del stream.

    Ciò significa che il blocco if non viene preso in considerazione, poiché se si passa attraverso uno dei percorsi dell’istruzione if , è ansible raggiungere l’istruzione di stampa finale. Se hai cambiato il tuo codice per essere:

     public void foo() { System.out.println("Hello"); if (true) return; else return; System.out.println("World!"); } 

    poi improvvisamente non si compilerebbe più, poiché non esiste un percorso attraverso l’istruzione if che consentirebbe di raggiungere l’ultima riga.

    Cioè, un compilatore compatibile con Java non è autorizzato a compilare il tuo primo frammento di codice. Per citare ulteriormente il JLS:

    Ad esempio, la seguente dichiarazione genera un errore in fase di compilazione:

     while (false) { x=3; } 

    perché l’istruzione x = 3; non è raggiungibile; ma il caso superficialmente simile:

     if (false) { x=3; } 

    non risulta in un errore in fase di compilazione. Un compilatore ottimizzante può rendersi conto che l’istruzione x = 3; non verrà mai eseguito e potrebbe scegliere di omettere il codice per tale istruzione dal file di class generato, ma l’istruzione x = 3; non è considerato “irraggiungibile” nel senso tecnico qui specificato.

    Il secondo avvertimento che Eclipse fornisce, sul codice morto, è un avvertimento generato dal compilatore, che non è “irraggiungibile”, secondo il JLS, ma in pratica lo è. Questo è un ulteriore controllo dello stile del lint fornito da Eclipse. Questo è del tutto opzionale e, usando la configurazione di Eclipse, può essere disabilitato, o trasformato in un errore del compilatore invece di un avvertimento.

    Questo secondo blocco è un “odore di codice”, if (false) blocchi vengono normalmente inseriti per disabilitare il codice per scopi di debug, averlo lasciato tipicamente accidentale, e quindi l’avvertimento.

    Infatti, Eclipse esegue test ancora più avanzati per determinare i possibili valori per un’istruzione if per determinare se sia ansible prendere entrambi i percorsi. Ad esempio, Eclipse si lamenterebbe anche del codice morto nel seguente metodo:

     public void foo() { System.out.println("Hello"); boolean bool = Random.nextBoolean(); if (bool) return; if (bool || Random.nextBoolean()) System.out.println("World!"); } 

    Genererà un codice irraggiungibile per la seconda istruzione if, poiché può ragionevolmente che bool debba essere false solo a questo punto del codice. In un frammento di codice così breve è ovvio che le due istruzioni if ​​stanno testando la stessa cosa, tuttavia se ci sono 10-15 linee di codice nel mezzo potrebbe non essere più così ovvio.

    Quindi, in sintesi, la differenza tra i due: uno è vietato dal JLS, e uno non lo è, ma viene rilevato da Eclipse come un servizio al programmatore.

    Questo per consentire un tipo di compilazione condizionale .
    Non è un errore con if , ma il compilatore mostrerà un errore per while , do-while e for .
    Questo va bene:

     if (true) return; // or false System.out.println("doing something"); 

    Questi sono errori

     while (true) { } System.out.println("unreachable"); while (false) { System.out.println("unreachable"); } do { } while (true); System.out.println("unreachable"); for(;;) { } System.out.println("unreachable"); 

    È spiegato alla fine di JLS 14.21: Dichiarazioni irraggiungibili :

    La logica di questo diverso trattamento è di consentire ai programmatori di definire “variabili di flag” come:

      static final boolean DEBUG = false; 

    e quindi scrivere il codice come:

      if (DEBUG) { x=3; } 

    L’idea è che dovrebbe essere ansible modificare il valore di DEBUG da falso a vero o da vero a falso e quindi compilare il codice correttamente senza altre modifiche al testo del programma.

    Il if (true) è un po ‘più sottile di “irraggiungibile”; poiché tale return codificata renderà sempre il codice seguente irraggiungibile, ma modificando la condizione in if potrebbe rendere raggiungibile la seguente dichiarazione.

    Avere un condizionale significa che c’è una possibilità che potrebbe cambiare. Ci sono casi in cui qualcosa di più complicato di un true è tra parentesi, e non è ovvio per il lettore umano che il seguente codice è “attenuato”, ma il compilatore si accorge, quindi è in grado di avvertirti.

    Eclipse è menzionato qui e rende le cose un po ‘più complicate per l’utente; ma in realtà sotto Eclipse è solo un (molto sofisticato) compilatore Java che ha molti interruttori per gli avvisi ecc. che Eclipse può accendere e spegnere. In altre parole, non si ottiene abbastanza l’ampiezza di diversi avvisi / errori da una compilazione dritta di javac , né si dispone di mezzi convenienti per accenderli o distriggersrli. Ma è lo stesso affare, solo con più campane e fischietti.

    Penso che un modo per farlo è che il codice irraggiungibile è molto probabilmente un errore, e il JLS cerca di proteggerti da tali errori.

    Permettere il if (true) return; è un buon modo per aggirare la limitazione di JLS se in realtà vuoi farlo apposta. Se il JLS lo fermasse, sarebbe d’intralcio. Inoltre, dovrebbe anche fermarsi:

      public static boolean DEBUG = true; //In some global class somewhere else ... if (DEBUG) return; //in a completely unrelated class. ... 

    Perché la costante DEBUG è completamente allineata e funzionalmente equivalente alla semplice digitazione di un vero in quella condizione. Da una prospettiva JLS questi due casi sono molto simili.

    La differenza è nella semantica tra tempo di esecuzione e tempo di compilazione. Nel tuo secondo esempio, il codice viene compilato su un ramo if-else nel bytecode e eclipse è semplicemente abbastanza intelligente da dirti che la parte else non verrà mai raggiunta in runtime. Eclipse ti avvisa solo perché è ancora un codice legale.

    Nel tuo primo esempio, si tratta di un errore perché il codice è illegale dalla definizione di java. Il compilatore non ti consente di creare codice byte con istruzioni non raggiungibili.

    Ho fatto qualche tentativo su eclipse e penso che ci siano 3 tipi di gestione dei codici morti di JDK: 1) no warn, 2) warn e 3) error.

    Per un tipico codice di compilazione condizionale “IF”, JDK lo rileva e non lo segnala come codice morto. Per un codice morto causato da un flag booleano costante, JDK lo rileva e lo segnala a livello di avviso. Per il codice morto causato dal stream di controllo del programma, JDK lo rileva come errore.

    Di seguito è il mio tentativo:

      public class Setting { public static final boolean FianlDebugFlag = false; } class B { ..... // no warn, it is typical "IF" conditional compilataion code if(Setting.FianlDebugFlag) System.out.println("am i dead?"); if(false) System.out.println("am i dead?"); // warn, as the dead code is caused by a constant boolean flag if(ret!=null && Setting.FianlDebugFlag) System.out.println("am i dead?"); if(Setting.FinalDebug) return null; System.out.println("am i dea?"); // error, as the dead code is due to the program's control flow return null; System.out.println("am i dead"); } 

    Se si desidera ignorare l’avviso “avviso di dead code in Java sotto Eclipse”, effettuare le seguenti operazioni all’interno di eclipse *:

    1. Fare clic su Window-Preferences-Java-Compiler-Errors / Warnings
    2. Clicca su “Potenziali problemi di programmazione”
    3. Scegli “Ignora” il “Codice Morto ad es. Se (falso)”
    4. Fai clic su Applica
    5. Clicca OK

    Salva e chiudi l’IDE di eclipse Quando riapri l’eclipse, questi avvisi specifici non dovrebbero più essere elencati.

    * Per questa soluzione di esempio sto usando Eclipse IDE per Java Developers – Versione: Mars.2 Release (4.5.2)