Skip to content

bad readline() returns empty string; expected undef #17937

Open
@jkeenan

Description

@jkeenan

Consider the following program, which is adapted from t/op/readline.t in the Perl 5 core distribution and which appears to have been in core since fd2c61bcfdb in July 2010.

$ cat bad-readline.t 
use strict;
use warnings;
use Test::More;

# [perl #72720] Test that sv_gets clears any variables that should be
# empty so if the read() aborts with EINTER, the TARG is actually
# cleared.
sub test_eintr_readline {
    my ( $fh, $timeout ) = @_;

    # This variable, the TARG for the readline is the core of this
    # test. The test is to see that after a my() and a failure in
    # readline() has the variable revived old, "dead" values from the
    # past or is it still undef like expected.
    my $line;

    # Do a readline into $line.
    if ( $timeout ) {

        # Do a SIGALARM aborted readline(). The underlying sv_gets()
        # from sv.c will use the syscall read() while will exit early
        # and return something like EINTR or ERESTARTSYS.
        my $timed_out;
        my $errno;
        eval {
            local $SIG{ALRM} = sub {
                $timed_out = 1;
                die 'abort this timeout';
            };
            alarm $timeout;
            undef $!;
            $line = readline $fh;
            $errno = $!;
            alarm 0;
        };
    
        # The code should have timed out.
        if ( ! $timed_out ) {
            warn $@
                    ? "$@: $errno\n"
                    : "Interrupted readline() test couldn't get interrupted: $errno";
        }
    }
    else {
        $line = readline $fh;
    }
    return $line;
}

SKIP: {

    # Connect two handles together.
    my ( $in, $out );
    my $piped;
    eval {
        pipe $in, $out;
        $piped = 1;
    };
    if ( ! $piped ) {
        skip( 2, 'The pipe function is unimplemented' );
    }

    # Make the pipe autoflushing
    {
        my $old_fh = select $out;
        $| = 1;
        select $old_fh;
    }

    # Only one line is loaded into the pipe. It's written unbuffered
    # so I'm confident it'll not be buffered.
    syswrite $out, "once\n";

    # Buggy perls will return the last thing successfully
    # returned. Buggy perls will return "once\n" a second (and
    # "infinitely" if we desired) as long as the internal read()
    # syscall fails. In our case, it fails because the inner my($line)
    # retains all its allocated space and buggy perl sets SvPOK to
    # make the value valid but before it starts read().
    my $once  = test_eintr_readline( $in, 0 );
    my $twice = test_eintr_readline( $in, 1 );
    is( $once,  "once\n", "readline read first line ok" );

    TODO: {
        local our $TODO = "bad readline returns '', not undef";
        is( $twice, undef,   "readline didn't return first line again" );
    }
}

done_testing();

We run the program.

$ perl bad-readline.t 
ok 1 - readline read first line ok
not ok 2 - readline didn't return first line again # TODO bad readline returns '', not undef
#   Failed (TODO) test 'readline didn't return first line again'
#   at bad-readline.t line 86.
#          got: ''
#     expected: undef
1..2

So we have a failing, TODO-ed test -- but I couldn't locate any bug ticket corresponding to it. (https://github.com/Perl/perl5/issues?q=is%3Aopen+in%3Atitle+readline+)

Hence, opening this ticket.

Thank you very much.
Jim Keenan

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions