This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Lib/warnings.py _show_warning does not protect against being called with a file like object which is closed
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.4, Python 3.5, Python 2.7
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: Julius.Lehmann-Richter, brett.cannon, mhammond, pitrou, terry.reedy, wolma
Priority: normal Keywords:

Created on 2014-08-29 12:29 by Julius.Lehmann-Richter, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Messages (13)
msg226058 - (view) Author: Julius Lehmann-Richter (Julius.Lehmann-Richter) Date: 2014-08-29 12:29
In Lib/warnings.py the _show_warning function catches IOError with the commented intention to ward against an "invalid file":

def _show_warning(message, category, filename, lineno, file=None, line=None):
    """Hook to write a warning to a file; replace if you like."""
    if file is None:
        file = sys.stderr
    try:
        file.write(formatwarning(message, category, filename, lineno, line))
    except IOError:
        pass # the file (probably stderr) is invalid - this warning gets lost.

If for some reason the file like object, and in the default case stderr, is closed, a calling program is faced with a ValueError, which is not being caught.

It seems to me, and correct me if I am wrong, that a file object which has been closed is a case of an "invalid file" and that the warning subsystem should in that case behave in the same manner as in the case of the IOError.

This behavior is the same for python 3.2 with the function renamed to showwarning and can be reproduced with for example

from sys import stderr                      
from warnings import warn
                     
stderr.close()
try:
    warn("foo")
except ValueError as e:
    print(e)
msg226073 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-08-29 20:31
3.1-3.3 only get security fixes. 3.4 outputs "write to closed file", so it appears to have the same issue. The proposed change seems reasonable to me, as a bugfix.
msg226111 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2014-08-30 01:21
I frankly don't think silencing more exceptions is a good idea. If someone decided to close sys.stderr without setting sys.stderr to None I'd say it's a programming error and deserves to be notified...
msg226113 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2014-08-30 01:27
Oops, sorry for the bogus un-nosying.
msg226116 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-08-30 03:39
If sys.stderr is set to None, then "if file is None: file = sys.stderr" will have no effect and 'file.write' raises "AttributeError: 'NoneType' object has no attribute 'write'" (actual quote).  Do you propose to catch that?  If not, it is not much of an improvement. Multiple people have reported on python-list that they are mystified by messages like this when they never used a None object as an argument.

I am sympathetic to not overlooking bugs.  However, a traceback is normally lost in this situation anyway.  If sys.stderr is closed, trying to write a ValueError traceback (or any other traceback) to stderr generates another ValueError.  Python apparently tries to write a double traceback to sys.__stderr__ as a backup (see below).  If that is also unusable (perhaps because it *is* sys.stderr), then the traceback disappears anyway.  This is what happens (on Windows, at least) with python started from either an icon or in Command Prompt.

>>> import sys; sys.stderr.close()
>>> from warnings import warn; warn('foo')
>>> sys.stderr is sys.__stderr__
True

When Idle starts an icon, the result is the same except that the user process is restarted.  Also, two streams are not the same; sys.__stderr__ is None.

If Idle is run with python from a console, there is still no message in the Idle shell but since sys.__stderr__ points back to the console, the double traceback appears there: first the warn ValueError, then 'while proccessing...', the traceback ValueError.  Idle replaces showwarning.  I believe it now only changes the formatting, but I could have it use sys.__stderr__ when not None to print the warning instead of or in addition to the tracebacks.

If the code above is placed in a file and run in batch mode, python quits with no output.

C:\Programs\Python34>python temop.py

C:\Programs\Python34>

When run from the idle editor running under pythonw, the user process does the same (exit without output).
msg226128 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2014-08-30 08:27
This is not about stderr though, this is about the `file` argument that is passed to showwarning(). That stderr may be an invalid file is a rather rare condition, for good reason (even if you want to silence any program output, it is generally better to redirect stderr to /dev/null rather than make it an invalid or closed file).

It would be better to research why the original except clause was added than try to blindly extend it, IMO.
msg226129 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2014-08-30 08:30
And so the original silencing came without a bug reference or an explanation:

changeset:   25204:2e7fe55c0e11
branch:      legacy-trunk
user:        Mark Hammond <mhammond@skippinet.com.au>
date:        Wed Sep 11 13:22:35 2002 +0000
files:       Lib/warnings.py
description:
Ignore IOError exceptions when writing the message.


I'm nosying Mark Hammond in case he remembers what his changeset addressed, but it's 12 years old and may not be relevant anymore.
msg281572 - (view) Author: Wolfgang Maier (wolma) * Date: 2016-11-23 17:29
Issue23016 fixed the "AttributeError: 'NoneType' object has no attribute 'write'" problem when sys.stderr is None.
With that it's now possible and good practice to set sys.stderr = None when closing it, just as Antoine suggested.
Should this issue be closed then?
msg281690 - (view) Author: Julius Lehmann-Richter (Julius.Lehmann-Richter) Date: 2016-11-25 09:33
Hey Wolfgang,

thank you for looking into this old one again ;)

The argument you are making does not answer the original bug report though as far as I can see.

My initial problem was not the AttributeError about the missing write but a ValueError (I/O operation on closed file).

Also this seems to have started a discussion about good programming practice between Terry and Antoine, my initial argument though was that, when you are catching an IOError for an invalid file, why not catch the ValueError for the closed file (since a closed file is surely not a valid target for a write operation).

I would really like to be educated on this if I am missing something, why does the argument for not silencing errors and expecting good programming practice apply to the ValueError of a closed standard error which has not been set to None but not to an IOError of a passed in file object?

Cheers Julius
msg281692 - (view) Author: Wolfgang Maier (wolma) * Date: 2016-11-25 09:58
Hi Julius,

I guess it's a question of control and responsibilities.
If you write a program that may issue warnings under some conditions, that program may, for example, be used with stderr redirected to a file. Now it is possible that file gets removed while your program is running, then, upon the next warning, the program would crash with an IOError. In effect, this would let external cirumstances that are not under control by the programmer escalate a non-critical warning message to a true exception. It's debatable whether that should be prevented or not, but that seems to be the decision that Mark Hammond made long ago.
If, OTOH, your program chooses to close sys.stderr, that is a deliberate decision and it's the programmers responsibility then not to try to write to it anymore. Suppressing the resulting ValueError would only hide the fact that due to a programming error no warnings can be issued anymore.
Not sure if this makes any sense to you or others, but that's how I came to think about this.
msg281693 - (view) Author: Wolfgang Maier (wolma) * Date: 2016-11-25 10:18
Ah, I just found Issue607668, which discusses the changeset 2e7fe55c0e11 that introduced IOError suppression. The actual use case at the time was different and I have no idea whether it would still be a problem today.
msg281698 - (view) Author: Julius Lehmann-Richter (Julius.Lehmann-Richter) Date: 2016-11-25 11:05
Hey Wolfgang,

thank you for the clarification.

I would never write such code of course, I stumbled across this with a third-party program.

I understand your point of view and I actually agree. I would not have even opened this report if it had not been for that comment.

If the error is still reproducable in the software I will open a bug with them instead and tell them to please fix it :P

As far as I am concerned this is not a bug (any more)

Cheers Julius
msg281703 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2016-11-25 11:33
In 3.6, the code in question is

def _showwarnmsg_impl(msg):
    file = msg.file
    if file is None:
        file = sys.stderr
        if file is None:
            # sys.stderr is None when run with pythonw.exe:
            # warnings get lost
            return
    text = _formatwarnmsg(msg)
    try:
        file.write(text)
    except OSError:
        # the file (probably stderr) is invalid - this warning gets lost.
        pass

When the write is attempted, *file* is not None.  It can either be sys.stderr or something else, successfully opened.  If writing to sys.stderr fails, we properly should give up.

If writing to something else fails, I now think we should try to write an exception to stderr.  So I thing the bug here, if any, is unconditionally passing.  Perhaps the exception clause should be replaced (in a new issue) with something like

     except (OSError, ValueError):
        if msg.file is not sys.stderr:
            raise
History
Date User Action Args
2022-04-11 14:58:07adminsetgithub: 66494
2016-11-25 11:33:14terry.reedysetstatus: open -> closed
resolution: not a bug
messages: + msg281703

stage: test needed -> resolved
2016-11-25 11:05:34Julius.Lehmann-Richtersetmessages: + msg281698
2016-11-25 10:18:16wolmasetmessages: + msg281693
2016-11-25 09:58:33wolmasetmessages: + msg281692
2016-11-25 09:33:50Julius.Lehmann-Richtersetmessages: + msg281690
2016-11-23 17:29:55wolmasetmessages: + msg281572
2016-11-22 16:46:19wolmasetnosy: + wolma
2014-08-30 08:30:28pitrousetnosy: + mhammond
messages: + msg226129
2014-08-30 08:27:52pitrousetmessages: + msg226128
2014-08-30 03:39:43terry.reedysetmessages: + msg226116
2014-08-30 01:27:03pitrousetnosy: + terry.reedy, Julius.Lehmann-Richter
messages: + msg226113
2014-08-30 01:21:15pitrousetnosy: + brett.cannon, pitrou, - terry.reedy, Julius.Lehmann-Richter
messages: + msg226111
2014-08-29 20:31:51terry.reedysetversions: + Python 3.4, Python 3.5, - Python 3.2
nosy: + terry.reedy

messages: + msg226073

stage: test needed
2014-08-29 12:29:03Julius.Lehmann-Richtercreate