Предупреждение о мертвом коде в try-with-resources, но не в переводе try-catch-finally

В следующем коде используется try Конструкция -with-resources появилась в Java 8. Метод occasionallyThrow() объявляется для создания OccasionalException, Resource' s метод close() для создания CloseException. Eclipse (версия: Neon Release (4.6.0), идентификатор сборки: 20160613-1800) добавляет в строку, отмеченную // мертвый код, предупреждение о том, что ветка является мертвым кодом. Неявно Eclipse подтверждает, что строка, отмеченная // действующим кодом, не мертвый код.

Object tryWithResources() throws OccasionalException {
    Object value = null;
    try (Resource resource = new Resource()) {
        occasionallyThrow();
        value = new Object();
    }
    catch (CloseException e) {
        if (value == null) {
            // alive code
        }
        else {
            // dead code
        }
    }
    return value;
}

Я смущен этим. Если occasionallyThrow() выдает свое OccasionalException, то try-с-ресурсами должен перехватить это как основное исключение, а затем попытаться закрыть ресурс. Если закрытие ресурса вызывает CloseException, оно будет подавлено с помощью OccasionalException, поэтому CloseException не будет перехватываться. Таким образом, CloseException должно быть перехвачено только тогда, когда блок внутри try успешно завершен, что означает, что value не равно нулю. Таким образом, кажется, что «мертвый код» на самом деле жив, а «живой код» на самом деле мертв. Я не уверен, что компилятор на самом деле должен распознать здесь, но, по крайней мере, кажется, что "мертвый код" здесь не следует называть мертвым.

Что делает это более сложным, так это то, что переведенная форма без использования формы try-with-resources вообще не помечается никакими предупреждениями о мертвом коде. (Я вполне уверен, что правильно понял этот перевод, основываясь на 14.20.3.2. Расширенная попытка с ресурсами, но я не удивлюсь, если здесь есть ошибка)

Object expandedTry() throws OccasionalException {
    Object value = null;
    try {
        Resource resource = new Resource();
        Throwable $primary = null;
        try {
            occasionallyThrow();
            value = new Object();
        }
        catch (Throwable t) {
            $primary = t;
            throw t;
        }
        finally {
            if (resource != null) {
                if ($primary != null) {
                    try {
                        resource.close();
                    }
                    catch (Throwable $suppressed) {
                        $primary.addSuppressed($suppressed);
                    }
                }
                else {
                    resource.close();
                }
            }
        }
    }
    catch (CloseException e) {
        if (value == null) {
            // alive (not dead!)
        }
        else {
            // alive
        }
    }
    return value;
}

Я упустил что-то, что сделало бы любую ветвь в if-else мертвой в одном из них, но не в другом?

Полный код

Вот полный код с определениями вспомогательных типов исключений, класса Resource и класса верхнего уровня.

public class TestTryWithResources {

    /** Exception thrown by Resource's close() method */
    @SuppressWarnings("serial")
    static class CloseException extends Exception {}

    /** AutoCloseable declared to throw a CloseException */ 
    static class Resource implements AutoCloseable {
        @Override
        public void close() throws CloseException {}
    }

    /** An occasionally thrown exception */
    @SuppressWarnings("serial")
    static class OccasionalException extends Exception {}

    /** Method declared to throw an occasional exception */
    void occasionallyThrow() throws OccasionalException {}

    /*
     * Method using try-with-resources.  Eclipse warns that the 
     * portion marked with "// dead code" is Dead code.
     */
    Object tryWithResources() throws OccasionalException {
        Object value = null;
        try (Resource resource = new Resource()) {
            occasionallyThrow();
            value = new Object();
        }
        catch (CloseException e) {
            if (value == null) {
                // alive code
            }
            else {
                // dead code
            }
        }
        return value;
    }

    /*
     * Method not using try-with-resources.  This is the translation
     * of the try-with-resources in tryWithResources, according to 
     * [14.20.3 try-with-resources][1].  Eclipse does not warn about 
     * any of the code being Dead code.
     * 
     * [1]: https://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.20.3 
     */
    Object expandedTry() throws OccasionalException {
        Object value = null;
        try {
            Resource resource = new Resource();
            Throwable $primary = null;
            try {
                occasionallyThrow();
                value = new Object();
            }
            catch (Throwable t) {
                $primary = t;
                throw t;
            }
            finally {
                if (resource != null) {
                    if ($primary != null) {
                        try {
                            resource.close();
                        }
                        catch (Throwable $suppressed) {
                            $primary.addSuppressed($suppressed);
                        }
                    }
                    else {
                        resource.close();
                    }
                }
            }
        }
        catch (CloseException e) {
            if (value == null) {
                // alive
            }
            else {
                // alive
            }
        }
        return value;
    }
}

Ответы на комментарии

В Amin J предложен обходной путь использования ресурса после установки value для изменения анализа кода Eclipse. Однако это не работает. После использования ресурса, например, распечатав его, предупреждение о мертвом коде все еще присутствует как в Luna, так и в Neon:

Предупреждение о мертвом коде Luna, несмотря на использование ресурса после присвоения значения

Предупреждение о мертвом коде Neon, несмотря на использование ресурса после присвоения значения


person Joshua Taylor    schedule 20.09.2016    source источник
comment
Возможно, это Ошибка 366277 — [1.7] Неверный анализ мертвого кода/нулевого значения в try- with-resources, хотя ему уже 5 лет...   -  person Joshua Taylor    schedule 20.09.2016
comment
Я не уверен, сможете ли вы поймать исключение, которое выдается во время закрытия, которое выполняется с помощью try-with-resources в блоке catch, принадлежащем блоку try. Что произойдет, если вы измените CloseException на проверяемое исключение? А что произойдет, если вы дополнительно переместите ловушку на соседнюю попытку?   -  person mm759    schedule 20.09.2016
comment
@mm759 mm759 Сначала отвечаю на средний вопрос: CloseException является проверенным исключением. Я обеспечил это, определив его здесь как подкласс Exception (а не как подкласс RuntimeException).   -  person Joshua Taylor    schedule 20.09.2016
comment
@mm759 mm759 По первому вопросу поведение try-with-resources с уловом описано 14.20.3.2. Расширенная попытка использования ресурсов. try ResourceSpecification Block Catches Finally (это то, что я использую (без необязательного finally), переводится как try { try ResourceSpecification Block } Catches Finally. Таким образом, вы можете перехватить исключение, вызванное неявным закрытием, когда тело не выдало исключение, поэтому (второй вопрос) перевод уже переместил [s] улов в окружающий try [.]   -  person Joshua Taylor    schedule 20.09.2016
comment
В обеденный перерыв и не могу проверить. Всегда ли иногдаThrow(), вопреки названию, выбрасывается Случайное исключение? Если это так, не будет ли строка после нее недоступна, потому что tryWithResources() обрабатывает Случайное исключение, выбрасывая его? Это означало бы, что значение никогда не изменится с нуля и сделает блок else недоступным.   -  person Zachary    schedule 20.09.2016
comment
@Zachary На самом деле, в предоставленной реализации он никогда не генерирует, но объявляется с помощью throws OccasionalException. Но я пробовал оба способа (всегда бросал и никогда не бросал) с теми же результатами.   -  person Joshua Taylor    schedule 20.09.2016
comment
javac не генерирует предупреждение, поэтому это похоже на Eclipse. Ошибка, упомянутая @JoshuaTaylor, имеет статус новой, что означает, что она не исправлена?   -  person Rodney    schedule 20.09.2016
comment
@Rodney Это если это та же самая ошибка (на которую она похожа), но может быть какая-то разница, которую я не заметил. И, как вы упомянули, это помечено как НОВОЕ, даже не ПОДТВЕРЖДЕННОЕ, так что может быть какая-то разница.   -  person Joshua Taylor    schedule 20.09.2016
comment
Это может быть НЕ ПОДТВЕРЖДЕНО, но я могу лично подтвердить, что это все еще происходит в Eclipse 4.5.2. В частности, код, помеченный как мертвый код, явно генерирует выходные данные при запуске тестового примера. И снова только Eclipse генерирует предупреждение, а не javac   -  person Rodney    schedule 20.09.2016
comment
@Rodney Да, и 4.5.2 все еще отстает от версии, которую я использовал (Neon Release (4.6.0), как упоминалось в вопросе). Кажется, что это было вокруг некоторое время.   -  person Joshua Taylor    schedule 20.09.2016


Ответы (2)


По какой-то причине статический анализатор кода считает, что ресурс будет закрыт сразу после объявления попытки, в то время как согласно этот учебник ресурс закрывается после оператора.

Инструкция try-with-resources гарантирует, что каждый ресурс будет закрыт в конце инструкции.

Так, например, если вы измените свой код, чтобы использовать ресурс после значения (код ниже), он не будет предупреждать вас о мертвом коде (однако проверено на Eclipse Luna).

Object tryWithResources() throws OccasionalException {
    Object value = null;
    try (Resource resource = new Resource()) {
        occasionallyThrow();
        value = new Object();
        resource.someMethod(); // using the resource, so eclipse thinks it's not closed yet (correctly)
    }
    catch (CloseException e) {
        if (value == null) {
            // alive code
        }
        else {
            // dead code
        }
    }
    return value;
}

ОБНОВЛЕНИЕ

Это фактический код, который я тестировал с использованием ресурса (в данном случае читателя) после установки значения.

        Object val = null;

        try (BufferedReader reader = new BufferedReader(new FileReader("C:\\file.txt"))) {
            val = new Object();
            System.out.println("got here");
            reader.readLine();
        }
        catch(IOException e){
            System.out.println("io ex");
            if ( val == null){

            }
            else{

            }
        }
person Amin J    schedule 20.09.2016
comment
Использование ресурса после установки value не устраняет предупреждение о мертвом коде в Eclipse. Я обновил вопрос, чтобы показать, что предупреждение все еще присутствует. - person Joshua Taylor; 20.09.2016
comment
Тогда это может быть специфично для Eclipse Neon. Я пробовал это в Eclipse Luna. - person Amin J; 20.09.2016
comment
Нет, я пробовал и в Luna, и в Eclipse. Предупреждение присутствует в обоих из них. Я снова обновил свой вопрос, чтобы показать оба. - person Joshua Taylor; 20.09.2016
comment
Интересно. Я также пробовал в Неоне. Он исчезает и в Neon. Я обновил ответ, чтобы включить фактический код (я пытаюсь использовать BufferedReader, так как у меня нет вашего кода) - person Amin J; 20.09.2016
comment
Хорошо, я думаю, я знаю, что это такое. метод readLine() также выдает IOException, поэтому, вероятно, eclipse теперь убежден, что исключение может быть выброшено после установки значения. - person Amin J; 20.09.2016
comment
Да, такое использование может избавиться от предупреждения, но в моем примере я специально пытаюсь убедиться, что метод close() и методы в block() не вызывают тот же тип исключения. Вот почему я использую ресурс, метод close() которого выдает CloseException, и метод, который выбрасывает Случайное Исключение, так что мы можем быть уверены, что если мы видим CloseException, оно должно произойти во время автоматическое закрытие, и именно оно должно быть в том случае, когда тело завершилось успешно. - person Joshua Taylor; 20.09.2016

Это не отвечает на вопрос, почему Eclipse генерирует предупреждение о том, что это так, и должно ли оно быть или нет, но это обходной путь, который, по крайней мере, временно устраняет предупреждение. Вместо того, чтобы помещать условие в блок catch, вы можете вызвать другой метод с проверяемым значением, а также с исключением, и из этого метода проверить, является ли объект нулевым, а затем сделать все, что нужно. быть сделано:

Object tryWithResources() throws OccasionalException {
    Object value = null;
    try (Resource resource = new Resource()) {
        occasionallyThrow();
        value = new Object();
        System.out.println(resource.toString());
    }
    catch (CloseException e) {
        catchBlock(value, e);  // call auxiliary method
    }
    return value;
}

void catchBlock(Object value, CloseException e) {
    if (value == null) {
        // then
    }
    else {
        // else
    }
}
person Joshua Taylor    schedule 20.09.2016