Skip to content

Tied separator and arguments in join() give unexpected results #21458

Closed
@book

Description

@book

This is a bug report for perl, generated with the help of perlbug 1.43 running under perl 5.39.3.


Using a self-modifying tied variable as the separator in join gives unexpected results.

Description

While implementing PPC 0013, I've been exploring the behaviour of join with overloaded separator and arguments, and also with tied separator and arguments.

This case is about using a self-modifying (in FETCH) tied variable both as a separator and multiple times in the argument list.

Expected behavior

The following (passing) test case sets expectations:

  • FETCH is called once on a tied separator, and the returned string is used to join the arguments
  • a join with a single argument detected at compilation is replaced with an OP_STRINGIFY, so FETCH is never called
  • when the tied separator is also used in the argument list, FETCH is called once to get the separating string, and once for each occurence of the variable in the argument list
use Test::More;
    
# simple tied variable
{
    package S;
    our $fetched;
    sub TIESCALAR { my $x = '-';   $fetched = 0; bless \$x }
    sub FETCH     { my $y = shift; $fetched++;   $$y }

    package main;
    my $t;

    tie $t, 'S';
    is( join( $t, a .. c ), 'a-b-c', 'tied separator' );
    is( $S::fetched,    1,       'FETCH called once' );

    tie $t, 'S';
    is( join( $t, 'a' ), 'a', 'tied separator on single item join' );
    is( $S::fetched,     0,   'FETCH not called' );

    tie $t, 'S';
    is( join( $t, 'a', $t, 'b', $t, 'c' ),
        'a---b---c', 'tied separator also in the join arguments' );
    is( $S::fetched, 3, 'FETCH called 1 + 2 times' );
}

done_testing;

Steps to Reproduce

When the tied variable is self-modifying in FETCH, the result does not seem to follow the above expectations:

use Test::More;

# self-modifying tied variable
{

    package SM;
    our $fetched;
    sub TIESCALAR { my $x = "1";   $fetched = 0; bless \$x }
    sub FETCH     { my $y = shift; $fetched++;   $$y += 3 }

    package main;
    my $t;

    tie $t, "SM";
    is( join( $t, a .. c ), 'a4b4c', 'tied separator' );
    is( $SM::fetched,   1,       'FETCH called once' );

    tie $t, "SM";
    is( join( $t, 'a' ), 'a', 'tied separator on single item join' );
    is( $SM::fetched,    0,   'FETCH not called' );

    tie $t, "SM";
    is( join( $t, "a", $t, "b", $t, "c" ),
        'a474b4104c', 'tied separator also in the join arguments' );
    is( $SM::fetched, 3, 'FETCH called 1 + 2 times' );
}

done_testing;

In the last call to join, FETCH is called the expected number of times, but the result is not what we expect, based on the behaviour described in the first case:

#     expected: 'a474b4104c'
                  ^ separator (FETCH)
                   ^ tied argument (FETCH)
                    ^ separator (cached)
                      ^ separator (cached)
                       ^ tied argument (FETCH)
                         ^ separator (cached)

versus:

#          got: 'a477b7101c'
                  ^ separator (FETCH)
                   ^ tied argument (FETCH)
                    ^ ??? separator (previously fetched value?)
                      ^ ??? separator (same as before?)
                       ^ tied argument (FETCH)
                         ^ ???

Flags

  • category=core
  • severity=medium

Perl configuration

Site configuration information for perl 5.39.3:

Configured by book at Wed Sep  6 08:18:57 CEST 2023.

Summary of my perl5 (revision 5 version 39 subversion 3) configuration:
  Commit id: 008ae6a0ef7f87df8ee75ca523c6adf1ae0b768d
  Platform:
    osname=linux
    osvers=6.2.0-31-generic
    archname=x86_64-linux
    uname='linux zlopp 6.2.0-31-generic #31~22.04.1-ubuntu smp preempt_dynamic wed aug 16 13:45:26 utc 2 x86_64 x86_64 x86_64 gnulinux '
    config_args='-des -Dusedevel'
    hint=recommended
    useposix=true
    d_sigaction=define
    useithreads=undef
    usemultiplicity=undef
    use64bitint=define
    use64bitall=define
    uselongdouble=undef
    usemymalloc=n
    default_inc_excludes_dot=define
  Compiler:
    cc='cc'
    ccflags ='-fwrapv -fno-strict-aliasing -pipe -fstack-protector-strong -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64'
    optimize='-O2'
    cppflags='-fwrapv -fno-strict-aliasing -pipe -fstack-protector-strong -I/usr/local/include'
    ccversion=''
    gccversion='11.4.0'
    gccosandvers=''
    intsize=4
    longsize=8
    ptrsize=8
    doublesize=8
    byteorder=12345678
    doublekind=3
    d_longlong=define
    longlongsize=8
    d_longdbl=define
    longdblsize=16
    longdblkind=3
    ivtype='long'
    ivsize=8
    nvtype='double'
    nvsize=8
    Off_t='off_t'
    lseeksize=8
    alignbytes=8
    prototype=define
  Linker and Libraries:
    ld='cc'
    ldflags =' -fstack-protector-strong -L/usr/local/lib'
    libpth=/usr/local/lib /usr/lib/x86_64-linux-gnu /usr/lib /usr/lib64
    libs=-lpthread -ldl -lm -lcrypt -lutil -lc
    perllibs=-lpthread -ldl -lm -lcrypt -lutil -lc
    libc=/lib/x86_64-linux-gnu/libc.so.6
    so=so
    useshrplib=false
    libperl=libperl.a
    gnulibc_version='2.35'
  Dynamic Linking:
    dlsrc=dl_dlopen.xs
    dlext=so
    d_dlsymun=undef
    ccdlflags='-Wl,-E'
    cccdlflags='-fPIC'
    lddlflags='-shared -O2 -L/usr/local/lib -fstack-protector-strong'


---
@INC for perl 5.39.3:
    lib
    /usr/local/lib/perl5/site_perl/5.39.3/x86_64-linux
    /usr/local/lib/perl5/site_perl/5.39.3
    /usr/local/lib/perl5/5.39.3/x86_64-linux
    /usr/local/lib/perl5/5.39.3

---
Environment for perl 5.39.3:
    HOME=/home/book
    LANG=en_US.UTF-8
    LANGUAGE (unset)
    LC_ADDRESS=fr_FR.UTF-8
    LC_IDENTIFICATION=fr_FR.UTF-8
    LC_MEASUREMENT=fr_FR.UTF-8
    LC_MONETARY=fr_FR.UTF-8
    LC_NAME=fr_FR.UTF-8
    LC_NUMERIC=fr_FR.UTF-8
    LC_PAPER=fr_FR.UTF-8
    LC_TELEPHONE=fr_FR.UTF-8
    LC_TIME=fr_FR.UTF-8
    LD_LIBRARY_PATH (unset)
    LOGDIR (unset)
    PATH=/home/book/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin
    PERL_BADLANG (unset)
    SHELL=/bin/bash

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions