You all joke that this doesn’t happen in practice, but something like this literally just bit me and it took me a few too many minutes to figure out what was going on.<p>I use a bash script as my BROWSER which calls another bash script to launch or communicate with my browser that I run inside a container. The script that my BROWSER script calls has some debug output that it prints to stderr.<p>I use mutt as my email client and urlscan [0] to open URLs inside emails. Urlscan looks at my BROWSER environment variable and thus calls my script to open whatever URL I target. Some time recently, the urlscan author decided to improve the UX by hiding stderr so that it wouldn’t pollute the view, and so attempted to pipe it to `/dev/null`. I guess their original code to do this wasn’t quite correct and it ended up closing the child processes’ stderr.*<p>I generally use `set -e` (errexit) because I want my scripts to fail if any command fails (I consider that after an unhandled failure the script’s behavior is undefined, some other people disagree and say you should never use `set -e` outside of development, but I digress). My BROWSER scripts are no exception.<p>While my scripts handle non-zero returns for most things that can go wrong, I never considered that writing log messages to stdout or stderr might fail. But it did, which caused the script to die before it was able to launch my browser. For a few weeks I wasn’t able to use urlscan to open links. I was too lazy to figure out what was wrong, and when I did it took me a while because I looked into every possibility <i>except</i> this one.<p>Luckily this wasn’t a production app. But I know now it could just as feasibly happen in production, too.<p>I opened an issue[1] and it was fixed very quickly. I love open source!<p>*No disrespect to urlscan, it’s an awesome tool and bugs happen to all of us!<p>[0]: <a href="https://github.com/firecat53/urlscan" rel="nofollow">https://github.com/firecat53/urlscan</a><p>[1]: <a href="https://github.com/firecat53/urlscan/issues/122" rel="nofollow">https://github.com/firecat53/urlscan/issues/122</a>
I’m disappointed. I expected some obscure edgecase (like “Main is usually a function…” [1]) but instead that’s about scope handling, contract design and responsibility shift.<p>“Hello world” method simply calls an API to a text interface. It uses simple call, to a simple interface that is expected to be ever present. I don’t find any bug there. It won’t work if such interface isn’t available, is blocked or doesn’t exist. It won’t work on my coffee grinder nor on my screwdriver. It won’t work on my Arduino because there is no text interface neither.<p>Of course, one could argue that user might expect you to handle that error. That’s all about contracts and expectation. How should I deal with that? Is the “Hello world” message such important that the highest escalated scenario should be painted on the sky? I can imagine an awkward social game where we throw each other obscure challenges and call it a bug.<p>It’s nitpicking that even such simple code might fail and I get it. It will also fail on OOM, faulty hardware or if number of the processes on the machine hit the limit. Maybe some joker replaced bindings and it went straight to 3D printer which is out of material? _My expectations_ were higher based on the title.<p>Now allow me to excuse myself, I need to write an e-mail to my keyboard manufacturer because it seems like it has a bug which prevents it from working when slightly covered in liquid coffee.<p>[1]: <a href="http://jroweboy.github.io/c/asm/2015/01/26/when-is-main-not-a-function.html" rel="nofollow">http://jroweboy.github.io/c/asm/2015/01/26/when-is-main-not-...</a>
Enjoyable read for sure, but i think the question whether ot not this constitutes a bug or not is open for interpretation.<p>IMHO, it doesn't.<p>hello.c is written in a way that makes it very clear that the program doesn't care about error conditions in any way shape or form; the return value of printf is ignored, the output isn't flushed, the return of flushing isn't checked, noone looks at errno; ...so anything happening that could go wrong will go unreported, unless its something the OS can see (segfault, permission, etc.)<p>If I expect a program to do something (eg. handle IO errors) that its code says very clearly that it doesn't, that's not the programs fault.
To those perplexed by the behaviour of Java's Hello World, as Java is otherwise very careful with error handling, this is because System.out is a java.io.PrintStream, and that's its documented behaviour:[1]<p>> Unlike other output streams, a PrintStream never throws an IOException; instead, exceptional situations merely set an internal flag that can be tested via the checkError method.<p>So the correct Hello World would be:<p><pre><code> System.out.println("Hello World!");
if (System.out.checkError()) throw new IOException();
</code></pre>
While the behaviour of PrintStream cannot be changed (it goes back to Java 1.0, and I'm guessing that the intention was not to require handling exceptions when writing messages to the standard output), adding a method to obtain the underlying, unwrapped OutputStream might be an idea worth considering, as it would allow writing to the standard output just like to any file.<p>[1]: <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/PrintStream.html" rel="nofollow">https://docs.oracle.com/en/java/javase/17/docs/api/java.base...</a>
Imo this is because the responsibility is not clearly defined and can be argued upon.<p>If my program writes to the standard output, but you choose to redirect the pipe to a different location, is it my program’s responsibility to check what happens to the bytes AFTER the pipe?<p>After all: <i>my program did output everything as expected</i>. The part which fucked up was not part of my program.<p>I can see why some projects decide to not handle this bug.
Well that's precisely the mindset of C/C++. You have to think by yourself about everything that can go wrong with your code. And, man, lots of things can go wrong.<p>I find more modern languages so much less exhausting to use to write correct code.
This is one of the reasons zig doesn't have a print function and you have to deal with the error when using the stdout writer[0].<p>[0] <a href="https://zig.news/kristoff/where-is-print-in-zig-57e9" rel="nofollow">https://zig.news/kristoff/where-is-print-in-zig-57e9</a>
A real world example of catching (some, but certainly not all) fflush(), ferror() etc. cases is what "git" does at the end of its execution, the first highlighted line is where it's returning from whatever function implements a built-in ("status", "pull", "log" etc. etc.): :<a href="https://github.com/git/git/blob/v2.35.0/git.c#L464-L483" rel="nofollow">https://github.com/git/git/blob/v2.35.0/git.c#L464-L483</a><p>Doing something similar would be a good addition to any non-trivial C program that emits output on stdout and stderr.<p>In practice I haven't really seen a reason to exhaustively check every write to stdout/stderr as long as standard IO is used, and fflush() etc. is checked.<p>A much more common pitfall is when dealing with file I/O and forgetting to check the return value of close(). In my experience it's the most common case where code that <i>tries</i> to get it wrong actually gets it wrong, I've even seen code that checked the return value of open(), write() and fsync(), but forgot about the return value of close() before that fsync(). A close() will fail e.g. if the disk is full.
I couldn't see GNU Hello mentioned in the article or comments so far. I wonder how it fares bug-wise.<p><i>The GNU Hello program produces a familiar, friendly greeting. Yes, this is another implementation of the classic program that prints “Hello, world!” when you run it.</i><p><i>However, unlike the minimal version often seen, GNU Hello processes its argument list to modify its behavior, supports greetings in many languages, and so on. The primary purpose of GNU Hello is to demonstrate how to write other programs that do these things; it serves as a model for GNU coding standards and GNU maintainer practices.</i><p><a href="https://www.gnu.org/software/hello/" rel="nofollow">https://www.gnu.org/software/hello/</a>
It's a fun take, but a hyperbole nonetheless. hello.c is supposed to be run from a terminal and write back to it: there's always space to write. It's not meant to be part of a shell script, so the error status is irrelevant.<p>It does show that we take such examples a bit too literally: our feeble minds don't consider what's missing, until it's too late. That's a didactic problem. It only matters to certain kinds of software, and when we teach many people to program, most of them won't go beyond a few small programs. But perhaps the "second programming course" should focus a bit less on OOP and introduce error handling.
Should "hello world" return error if it actually prints something but there was no person to read the output? Maybe the user was distracted and was not looking at the screen. Does a "hello world" program make sound if no one hears it?<p>Sounds like the program failed its objective, greeting the world. And thus imho shouldn't return 0.
This raises an interesting question: is there any IO function that should return unit/void? Or equivalently are there any IO functions for which we can safely ignore the return value/ignore all exceptions?<p>It seems like every single IO thing I can think of can have a relevant error, regardless of whether it's file-system related, network, or anything else.
Since the article is being pedantic, here's another pedantic complaint: What if printf() can't write <i>all</i> of its output, but manages to write some of it? printf() returns the number of bytes written, but (and I'm sure someone will correct me if I'm wrong!) it doesn't guarantee to be atomic - it can't either write everything or nothing. Imagine a complicated printf() call with lots of parameters and long strings - some of it might get written, then the next write() that it does fails due to lack of space. What does printf() do then?<p>The article cites an example of writing a YAML file and the dangers of it being half-written. Well, you could imagine outputting a file all in one printf() with lots of %s's in the format string. Some get written, but not all. If printf() decides to return an error message, retrying the printf() later on (after deleting another file, say), will corrupt the data because you'll be duplicating some of the output. But if printf() just returned the number of bytes written, your program will silently miss the error.<p>So does 'Hello World\n' need to check that printf() succeeded, or does it actually need to go further and check that printf() returned 12? (or is it 13, for \r\n ?) I don't think there's any way to really safely use the function in real life.
> <i>There's our "No space" error getting reported by the OS, but no matter, the program silently swallows it and returns 0, the code for success. That's a bug!</i><p>Bzzt, no. You can't say that without knowing what the program's requirements are.<p>Blindly "fixing" a program to indicate failure due to not being able to write to standard output could break something.<p>Maybe the output is just a diagnostic that's not important, but some other program will reacts to the failed status, causing an issue.<p>Also, if a program produces output with a well-defined syntax, then the termination status may be superfluous; the truncation of the output can be detected by virtue of that syntax being incomplete.<p>E.g. JSON hello world fragment:<p><pre><code> puts("{\"hello\":\"world\"}");
return 0;
</code></pre>
if something is picking up the output and parsing it as JSON, it can deduce from a failed parse that the program didn't complete, rather than going by termination status.
This is good. All the people questioning the spec need to realize is handling the error should be opt-<i>out</i>, not opt-in.<p>#[must_use] in Rust is the right idea: Rust doesn't automatically do anything --- there is no policy foisted upon the programmer --- but it will reliably force the programmer to do <i>something</i> about the error explicitly.
Jim Meyering's classic "Goodbye World" talk on this<p><a href="https://www.gnu.org/ghm/2011/paris/slides/jim-meyering-goodbye-world.pdf" rel="nofollow">https://www.gnu.org/ghm/2011/paris/slides/jim-meyering-goodb...</a>
It would be more interesting if the post shows how to detect that error. (and how the other language examples look, at least on mobile I dont see them)
Even Golang de facto suffers from this. I don't think I can name a time I saw someone check the return value of fmt.Print or log.Print. Not checking the return value still seems the the "right" thing to do.
I was expecting Free Pascal not to have the bug, as Pascal generally fails with a visible runtime error, as it does I/O checking by default. However, it seems not to do it when WriteLn goes to the standard output... (even if it is then piped to /dev/full). So the basic<p><pre><code> begin
WriteLn('Hello World!');
end.
</code></pre>
definitely has the bug, at least with the fpc implementation. On the other hand, explicitly trying to write to /dev/full from the Pascal source triggers a beautiful message:<p><pre><code> Runtime error 101 at $0000000000401104</code></pre>
Do not forget the notes on dup2(). It's about the automatic closing of newfd before it gets replaced. I've bumped into this situation several times, that is why I'm mentioning it.<p><pre><code> SYNOPSIS
int dup2(int oldfd, int newfd);
NOTES:
If newfd was open, any errors that would have been reported at close(2) time are lost.
If this is of concern, then the correct approach is not to close newfd before calling dup2(),
because of the race condition described above.
Instead, code something like the following could be used:
/* Obtain a duplicate of 'newfd' that can subsequently
be used to check for close() errors; an EBADF error
means that 'newfd' was not open. */
tmpfd = dup(newfd);
if (tmpfd == -1 && errno != EBADF) {
/* Handle unexpected dup() error */
}
/* Atomically duplicate 'oldfd' on 'newfd' */
if (dup2(oldfd, newfd) == -1) {
/* Handle dup2() error */
}
/* Now check for close() errors on the file originally
referred to by 'newfd' */
if (tmpfd != -1) {
if (close(tmpfd) == -1) {
/* Handle errors from close */
}
}</code></pre>
Since we are going pedantic here, here are 3 bugs that I found in the blogpost:<p>1. Node.js result is out-dated. I run on Node.js v14.15.1 hello world code below on macOS and it reported exit code 1 correctly:<p><pre><code> // testlog.js
console.log('hello world')
process.exit(0)
// bash
$ node -v
v14.15.1
$ node testlog.js > /dev/full
-bash: /dev/full: Operation not permitted
$ echo $?
1
</code></pre>
2. Node.js is not a language. JavaScript is a language, and Node.js is a JavaScript runtime environment that runs on the V8 engine and executes JavaScript code outside a web browser.<p>3. Missing JavaScript result in the table, which is the most popular language on GitHub: <a href="https://octoverse.github.com/#top-languages-over-the-years" rel="nofollow">https://octoverse.github.com/#top-languages-over-the-years</a>
Julia (1.7) behaves pretty similar to the Python 2 one, a printed error related to closing the stream, and a 0 error code.<p><pre><code> ~ >>> julia -e 'print("Hello world")' > /dev/full
error in running finalizer: Base.SystemError(prefix="close", errnum=28, extrainfo=nothing)
#systemerror#69 at ./error.jl:174
systemerror##kw at ./error.jl:174
systemerror##kw at ./error.jl:174
#systemerror#68 at ./error.jl:173 [inlined]
systemerror at ./error.jl:173 [inlined]
close at ./iostream.jl:63
⋮
~ >>> echo $?
0
</code></pre>
`errnum=28` apparently refers to the ENOSPC error: "No space left on device" as defined by POSIX.1; so the information is there, even if not in the most presentable form.
In the end, it states that the language C has the bug. But this is wrong. In C, there are no exceptions, i.e. all error checking has to be explicit. This is just the language. So when you now ignore the error, this is not a bug of the language but just a bug in your code. The only thing you could argue is that this is a bad language design.<p>Or maybe this about global stdout object. With buffering enabled (by default), printf will not throw any error. The fflush would do. But a final fflush would be done implicitly at the end. But this is again all well documented, so still, this is not really a bug but maybe just bad language design.<p>I'm not exactly sure what C++ code was used. If this was just the same C code, then the same thing applies. And iostream just behaves exactly as documented.
An interesting link in the article: "Main is usually a function. So then when is it not?"<p><a href="https://news.ycombinator.com/item?id=27504254" rel="nofollow">https://news.ycombinator.com/item?id=27504254</a>
Seems like PHP does something reasonably right for once!<p><pre><code> $ php hello.php > /dev/full
$ echo $?
255
</code></pre>
It doesn't exactly print an error, but at least it returns something non-zero.
Still confused. It seems some people think there is nothing to fix, some think the programmer needs to act to prevent it, some think ANSI and other creators of the affected languages would need to act to prevent it.<p>If we accept the idea that the function (non-coding use of the word) of an language's indication of success should - indicate success (or its absence) - of a piece of code, then surely the creators of the languages should make it do just that. That's their job right no? What am I missing?
Ignoring the return of printf is a "bug". For the hello-world example, you can simply pass the printf value to main: "return printf(...) > 0;"
It's only a bug if the requirements are:<p>"Print Hello World and indicate if it succeed or not"<p>If the requirements were:<p>"Print Hello World, then return 0"<p>It's working as intended.<p>I'd even go so far as to say that print(); return 0; should always return 0, it would be weird for such a program to ever return anything other than 0 (where would that return come from?).
/dev/full is brilliant, I need to remember that!<p>On the other hand, it's kind of depressing that I can't even write to stdout without needing to check for errors. And what are you going to do if that fails? Write to stderr? What if that fails because the program was run with `2>&1` ?
It's a good post, but heavily OS dependant. For example, on my Mac:<p><pre><code> $ ls /dev/null /dev/full
ls: /dev/full: No such file or directory
/dev/null
</code></pre>
I guess in theory, you can imitate `/dev/full` by other means.
I don’t think this is a bug. The program writes to stdout which is guaranteed by *ix to be there. If it is full, it would block until the kernel serviced it. In your examples, the user asked the shell to redirect to a full file and it reported the error.
Doesn't seem like anyone has posted the bug free implementation so here it is:<p><pre><code> int main(void)
{
char the_terminal[] = "Hello World!\n"
return 0;
}</code></pre>
This is (IMHO) the difference between unit testing and other testing.<p>Unit testing verifies it does what it is supposed to do <i>ideally</i>, and all other tests verify it can do it in non-ideal environments.
What happens if in the original hello world example, you unplugged the monitor?<p>Would any of the languages report an error?<p>Maybe they all have bugs.
The author missed another bug, which many others do. You need a comma after Hello, as in "Hello, World!" because it's a direct address. Very, and I mean VERY few books get this right.<p>0. <a href="https://www.grammar-monster.com/lessons/commas_with_vocative_case.htm" rel="nofollow">https://www.grammar-monster.com/lessons/commas_with_vocative...</a>