Printing to STDOUT and STDERR can panic
When printing to STDOUT or STDERR, the operation may panic. The print
method
performs two writes: one to write the message, and one to write a newline. We
chose this approach so we don't have to concatenate the string first, as doing
so could be inefficient for large strings.
The first write is wrapped in a try ... else
, allowing it to handle errors.
But the second write (for the newline) is not. To illustrate how this can panic,
we use the following code:
import std::stdio::stdout
stdout.print('foo')
stdout.print('bar')
stdout.print('baz')
We then run it as follows:
inko test.inko | head -n1
This then produces the following output:
foo
Stack trace (the most recent call comes last):
0: "/tmp/test.inko", line 5, in "main"
Process 0x55f4768427c0 panicked: A thrown value reached the top-level in process 0x55f4768427c0
The reason the compiler doesn't handle this is because it doesn't know that
_INKOC.stdout_write
may throw an error.
While I couldn't quite reproduce it when printing to STDERR, this may just be because of me not redirecting the output properly. Either way, the code is the same and thus would suffer from the same problem.
It appears that just the following diff is not enough to solve the problem:
diff --git a/runtime/src/std/stdio/stdout.inko b/runtime/src/std/stdio/stdout.inko
index 7ff3fc76..8ad8089c 100644
--- a/runtime/src/std/stdio/stdout.inko
+++ b/runtime/src/std/stdio/stdout.inko
@@ -60,7 +60,7 @@ impl Write for ThisModule {
def print(data: ?ToString = Nil) -> Integer {
process.blocking {
let written = try _INKOC.stdout_write(data.to_string) else return 0
- written + _INKOC.stdout_write(NEWLINE)
+ written + try _INKOC.stdout_write(NEWLINE) else 0
}
}
Using this diff, the code still panics. If we remove the process.blocking
usage, the code will run when piping to head -n1
, but still panic when piping
the output to head -n0
. Oddly enough, if we use the following diff the code
still panics (when using head -n0
):
diff --git a/runtime/src/std/stdio/stdout.inko b/runtime/src/std/stdio/stdout.inko
index 7ff3fc76..b28eca27 100644
--- a/runtime/src/std/stdio/stdout.inko
+++ b/runtime/src/std/stdio/stdout.inko
@@ -58,10 +58,8 @@ impl Write for ThisModule {
#
# stdout.print # => Nil
def print(data: ?ToString = Nil) -> Integer {
- process.blocking {
- let written = try _INKOC.stdout_write(data.to_string) else return 0
- written + _INKOC.stdout_write(NEWLINE)
- }
+ let written = try _INKOC.stdout_write(data.to_string) else return 0
+ written + try _INKOC.stdout_write(NEWLINE) else 0
}
def flush {
It's possible the try
expressions are not compiled properly. Alternatively,
perhaps in the VM code we handle EBROKENPIPE as a panic instead of an exception.
Expected behaviour
"foo" is written to STDOUT, the rest is ignored.
Actual behaviour
A panic occurs.
System information
Operating system and version used: ADD HERE
Output of ivm --version
HEAD
Output of inko --version
:
HEAD
Output of uname -a
:
Linux roach 5.6.15-arch1-1 #1 SMP PREEMPT Wed, 27 May 2020 23:42:26 +0000 x86_64 GNU/Linux
Output of cargo --version
:
cargo 1.43.0 (3532cf738 2020-03-17)