Bugs in Hello World
Posted on
Hello World might be the most frequently written computer program. For decades, it's been the first program many people write, when getting started in a new programming language.
Surely, this humble starting-point program should be bug free, right?
After all, hello world programs only do one thing. How could there be a bug?
Hello world in C
There are a lot of different ways to write hello world in C. There's the Wikipedia version, the hello world in the K&R book, and there's even the oldest known C hello world program from 1974.
Here's another, this one in "ANSI C":
/* Hello World in C, Ansi-style */
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
puts("Hello World!");
return EXIT_SUCCESS;
}
This is the most careful version of the bunch. It uses (void)
to ensure that
main
is a new-style declaration. It uses the EXIT_SUCCESS
macro instead
of just assuming that the platform uses 0 to indicate success, which isn't
necessary, according to the C standard, but we're not taking any chances here.
And it uses the appropriate headers to avoid implicitly declaring puts
. This
version attempts to do everything right.
And yet, it still has a bug.
All the versions linked above have a bug.
A bug?
Linux has this fun device file called "/dev/full", which is like its more famous cousin "/dev/null", but when you write to "/dev/full", instead of throwing away the data, it fails. It acts like a file on a filesystem that has just run out of space:
$ echo "Hello World!" > /dev/full
bash: echo: write error: No space left on device
$ echo $?
1
This is a great little tool for testing that programs handle I/O errors correctly. It's inconvenient to create actual filesystems with no space left, or disks that actually fail, but it's really easy to ask a program to write its output to "/dev/full" and see what happens.
So let's test the C example above:
$ gcc hello.c -o hello
$ ./hello > /dev/full
$ echo $?
0
Unlike when we used echo
in the shell above, here, we got no output, and
the exit status was zero. That means the hello
program reported successful
execution. However, it didn't actually succeed. We can confirm that it
encounters a failure using strace:
$ strace -etrace=write ./hello > /dev/full
write(1, "Hello World!\n", 13) = -1 ENOSPC (No space left on device)
+++ exited with 0 +++
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!
How severe is this bug? Arguably, hello world isn't going to be safety-critical anywhere. However, hello world does do something that programs in the real world do: print to standard output, which might be redirected to a file. And files in the real world can run out of space. If a program doesn't detect this kind of error and report it through its return code, its parent process won't know that the child failed, and will continue running as if nothing was wrong, even though the output it expected to have been produced has silently lost data.
For example, consider a program that prints a yaml file to standard output. If standard output runs out of space, the output may be truncated at some arbitrary point, though it may still be valid yaml. So we should expect programs to detect and report this kind of situation.
What about other languages?
We looked at bash and C above; what about Python, which tells us that "Errors should never pass silently"? Here's Python 2:
$ python2 hello.py > /dev/full
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr
$ echo $?
0
It did print a message to stderr, though it's a confusing message. However, it also returned 0, which means it's telling whoever ran it that it exited succeesfully.
Fortunately, Python 3 properly reports the error, and prints a nicer error message too:
$ python3 hello.py > /dev/full
Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>
OSError: [Errno 28] No space left on device
$ echo $?
120
Using hello world programs from common tutorial sites in a few languages that I happened to try, here are the results:
Language | Has the bug | Versions tested |
---|---|---|
C | Yes | (all) |
C++ | Yes | (all) |
Python 2 | Yes | Python 2.7.18 |
Ruby | Yes | ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux-gnu] |
Java | Yes | openjdk 11.0.11 2021-04-20 |
Node.js | Yes | v12.21.0 |
Haskell | Yes | The Glorious Glasgow Haskell Compilation System, version 8.8.4 |
Rust | No | rustc 1.59.0 (9d1b2106e 2022-02-23) |
Python 3 | No | Python 3.9.5 |
Perl | No | perl 5, version 32, subversion 1 (v5.32.1) built for x86_64-linux-gnu-thread-multi (with 46 registered patches...) |
Perl 6 | No | v2020.12 |
Bash | No | GNU bash, version 5.1.4(1)-release (x86_64-pc-linux-gnu) |
Awk | No | GNU Awk 5.1.0, API: 3.0 (GNU MPFR 4.1.0, GNU MP 6.2.1) |
OCaml | No | 4.08.1 |
Tcl | No | 8.6.11 |
C# | No | Mono JIT compiler version 6.8.0.105 |
A more complete and current list is maintained here.