Perl / perl5

🐪 The Perl programming language
https://dev.perl.org/perl5/
Other
1.92k stars 548 forks source link

panic when clearing array/hash during or-assignment #21658

Open genuaboro opened 10 months ago

genuaboro commented 10 months ago

Description I found some code that clears a hash in an eval block while trying to assign a value to an element of the hash. This produces a panic.

Steps to Reproduce

my (%cache, $i);
while (++$i < 100) {
    $cache{$i} ||= eval {
        %cache = () if keys %cache > 10;
        'x';
    };
}

panic: attempt to copy value x to a freed scalar 441d3381cc8

Variations of the code above that produce this panic: One liner: perl -e '$x{a}||=do{%x=()}' //= instead of ||=: perl -e '$x{a}//=do{%x=()}' eval instead of do: perl -e '$x{a}||=eval{%x=()}' map instead of do: perl -e '$x{a}||=map{%x=()}(0)' array instead of hash: perl -e '$x[0]||=do{@x=()}'

The panic does not occur if "=" is used instead of "||=": perl -e '$x{a}=do{%x=()}'

Expected behavior No panic.

Perl configuration

Found in perl 5.36.1 under OpenBSD. But also reproducable with Ubuntu 22.04 (perl 5.34.0), blead (5.39.6) and perl 5.8.9.

Summary of my perl5 (revision 5 version 36 subversion 1) configuration:

  Platform:
    osname=openbsd
    osvers=7.4
    archname=amd64-openbsd
    uname='openbsd'
    config_args='-dse -Dopenbsd_distribution=defined -Dusemymalloc -Dmksymlinks'
    hint=recommended
    useposix=true
    d_sigaction=define
    useithreads=undef
    usemultiplicity=undef
    use64bitint=define
    use64bitall=define
    uselongdouble=undef
    usemymalloc=y
    default_inc_excludes_dot=define
  Compiler:
    cc='cc'
    ccflags ='-DNO_LOCALE_NUMERIC -DNO_LOCALE_COLLATE -fno-strict-aliasing -fno-delete-null-pointer-checks -pipe -fstack-protector-strong -I/usr/local/include'
    optimize='-O2'
    cppflags='-DBIG_TIME -DNO_LOCALE_NUMERIC -DNO_LOCALE_COLLATE -fno-strict-aliasing -fno-delete-null-pointer-checks -pipe -fstack-protector-strong -I/usr/local/include'
    ccversion=''
    gccversion='OpenBSD Clang 13.0.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 ='-Wl,-E  -fstack-protector-strong -L/usr/local/lib'
    libpth=/usr/lib /usr/lib/clang/13.0.0/lib
    libs=-lm -lc
    perllibs=-lm -lc
    libc=/usr/lib/libc.so.97.1
    so=so
    useshrplib=true
    libperl=libperl.so.23.0
    gnulibc_version=''
  Dynamic Linking:
    dlsrc=dl_dlopen.xs
    dlext=so
    d_dlsymun=undef
    ccdlflags='-Wl,-R/usr/libdata/perl5/amd64-openbsd/CORE'
    cccdlflags='-DPIC -fpic '
    lddlflags='-shared -fpic  -fstack-protector-strong -L/usr/local/lib'

Characteristics of this binary (from libperl): 
  Compile-time options:
    HAS_TIMES
    MYMALLOC
    PERLIO_LAYERS
    PERL_COPY_ON_WRITE
    PERL_DONT_CREATE_GVSV
    PERL_MALLOC_WRAP
    PERL_OP_PARENT
    PERL_PRESERVE_IVUV
    USE_64_BIT_ALL
    USE_64_BIT_INT
    USE_LARGE_FILES
    USE_LOCALE
    USE_LOCALE_CTYPE
    USE_LOCALE_TIME
    USE_PERLIO
    USE_PERL_ATOF
  Built under openbsd
  @INC:
    /usr/local/libdata/perl5/site_perl/amd64-openbsd
    /usr/local/libdata/perl5/site_perl
    /usr/libdata/perl5/amd64-openbsd
    /usr/libdata/perl5
mauke commented 10 months ago

Reduced (v5.38.0):

$ perl -e '$x{a} ||= %x = ()'
panic: attempt to copy value 0 to a freed scalar 558ea0d9c408 at -e line 1.

There is an additional wrinkle in blead if you enable warnings:

$ ./perl -e '$x{a} ||= %x = ()'
panic: attempt to copy value 0 to a freed scalar 55d2e4a2b5e8 at -e line 1.
$ ./perl -we '$x{a} ||= %x = ()'
Useless assignment to a temporary at -e line 1.
$

The warning makes no sense in this context, so I suspect premature free / stack-not-refcounted shenanigans.

mauke commented 10 months ago

Can confirm that both the original code and my reduced form run through without errors on blead built with -Accflags='-DPERL_RC_STACK', so this is yet another manifestation of the "stack is not refcounted" bug.

jkeenan commented 10 months ago

And this has probably been present "forever":

$ perlbrew use perl-5.14.4
$ perl -e '$x{a} ||= %x = ()'
panic: attempt to copy value 0 to a freed scalar 558d31204078 at -e line 1.
jkeenan commented 10 months ago

Can confirm that both the original code and my reduced form run through without errors on blead built with -Accflags='-DPERL_RC_STACK, so this is yet another manifestation of the "stack is not refcounted" bug.

Observation confirmed (here, on FreeBSD-13):

$ ./perl -Ilib -v | head -2 | tail -1; ./perl -Ilib -V:config_args
This is perl 5, version 39, subversion 6 (v5.39.6 (v5.39.5-13-g7bb9a11330)) built for amd64-freebsd-thread-multi-ld
config_args='-des -Dusedevel -Duseithreads -Doptimize=-O2 -pipe -fstack-protector -fno-strict-aliasing -Dcc=gcc -Accflags=-DPERL_RC_STACK -Dusemorebits -DDEBUGGING';
$ ./perl -Ilib -e '$x{a} ||= %x = ()'
$ ./perl -Ilib -we '$x{a} ||= %x = ()'
Useless assignment to a temporary at -e line 1.