Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Storable's recursion check fires even in unproblematic cases #16606

Closed
p5pRT opened this issue Jul 3, 2018 · 12 comments
Closed

Storable's recursion check fires even in unproblematic cases #16606

p5pRT opened this issue Jul 3, 2018 · 12 comments

Comments

@p5pRT
Copy link

p5pRT commented Jul 3, 2018

Migrated from rt.perl.org#133326 (status was 'resolved')

Searchable as RT133326$

@p5pRT
Copy link
Author

p5pRT commented Jul 3, 2018

From @eserte

This is a bug report for perl from slaven@​rezic.de,
generated with the help of perlbug 1.41 running under perl 5.28.0.


The recursion check introduced in Storable 3.x croaks in cases
where there is no problematic recursion involved. A sample oneliner​:

$ perl5.28.0 -E 'use Storable qw(dclone); say "version=",$Storable​::VERSION; say "limit=",$Storable​::recursion_limit; my @​tt; for (1..16000) { my $t = [[[]]]; push @​tt, $t } dclone \@​tt'
version=3.08
limit=15692
Max. recursion depth with nested structures exceeded at -e line 1.

(Depending on the calculated recursion_limit the number of iterations
has to be adjusted)

This is not a problem with earlier Storable versions, even with much more
elements in this array​:

$ perl5.26.2 -E 'use Storable qw(dclone); say "version=",$Storable​::VERSION; say "limit=",$Storable​::recursion_limit; my @​tt; for (1..1_000_000) { my $t = [[[]]]; push @​tt, $t } dclone \@​tt'
version=2.62
limit=
(no output)

If $Storable​::DEBUGME is set (and a debugging perl is available),
then one can see that recur_depth is constantly increasing.

BTW​: Storable's bug queue currently points to rt.perl.org. But
https://rt.cpan.org/Public/Dist/Display.html?Name=Storable still
exists and has a lot of open issues, and I find it much easier
to find Storable-related issues in a specific bug queue than in
the huge and rather unstructured perl bug queue. For example,
even if we have the flag "module=..." created by perlbug
it is nowhere reflected in rt.perl.org (e.g. it's not possible
to search for this field, it seems).



Flags​:
  category=library
  severity=high
  module=Storable


Site configuration information for perl 5.28.0​:

Configured by eserte at Sat Jun 23 08​:28​:02 CEST 2018.

Summary of my perl5 (revision 5 version 28 subversion 0) configuration​:
 
  Platform​:
  osname=linux
  osvers=3.16.0-4-amd64
  archname=x86_64-linux
  uname='linux cabulja 3.16.0-4-amd64 #1 smp debian 3.16.51-3 (2017-12-13) x86_64 gnulinux '
  config_args='-ds -e -Dprefix=/opt/perl-5.28.0 -Dcf_email=srezic@​cpan.org'
  hint=recommended
  useposix=true
  d_sigaction=define
  useithreads=undef
  usemultiplicity=undef
  use64bitint=define
  use64bitall=define
  uselongdouble=undef
  usemymalloc=n
  default_inc_excludes_dot=define
  bincompat5005=undef
  Compiler​:
  cc='cc'
  ccflags ='-fwrapv -fno-strict-aliasing -pipe -fstack-protector-strong -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_FORTIFY_SOURCE=2'
  optimize='-O2'
  cppflags='-fwrapv -fno-strict-aliasing -pipe -fstack-protector-strong -I/usr/local/include'
  ccversion=''
  gccversion='4.9.2'
  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/gcc/x86_64-linux-gnu/4.9/include-fixed /usr/include/x86_64-linux-gnu /usr/lib /lib/x86_64-linux-gnu /lib/../lib /usr/lib/x86_64-linux-gnu /usr/lib/../lib /lib
  libs=-lpthread -lnsl -lgdbm -ldb -ldl -lm -lcrypt -lutil -lc -lgdbm_compat
  perllibs=-lpthread -lnsl -ldl -lm -lcrypt -lutil -lc
  libc=libc-2.19.so
  so=so
  useshrplib=false
  libperl=libperl.a
  gnulibc_version='2.19'
  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.28.0​:
  /opt/perl-5.28.0/lib/site_perl/5.28.0/x86_64-linux
  /opt/perl-5.28.0/lib/site_perl/5.28.0
  /opt/perl-5.28.0/lib/5.28.0/x86_64-linux
  /opt/perl-5.28.0/lib/5.28.0


Environment for perl 5.28.0​:
  HOME=/home/eserte
  LANG=en_US.UTF-8
  LANGUAGE=en_US​:en
  LD_LIBRARY_PATH (unset)
  LOGDIR (unset)
  PATH=/usr/local/bin​:/usr/bin​:/bin​:/usr/local/sbin​:/usr/sbin​:/sbin​:/home/eserte/bin/linux-gnu​:/home/eserte/bin/sh​:/home/eserte/bin​:/home/eserte/bin/pistachio-perl/bin​:/usr/games​:/home/eserte/devel
  PERLDOC=-MPod​::Perldoc​::ToTextOverstrike
  PERL_BADLANG (unset)
  SHELL=/bin/zsh

@p5pRT
Copy link
Author

p5pRT commented Jul 3, 2018

From @eserte

Dana Tue, 03 Jul 2018 01​:40​:45 -0700, slaven@​rezic.de reče​:

This is a bug report for perl from slaven@​rezic.de,
generated with the help of perlbug 1.41 running under perl 5.28.0.

-----------------------------------------------------------------
The recursion check introduced in Storable 3.x croaks in cases
where there is no problematic recursion involved. A sample oneliner​:

$ perl5.28.0 -E 'use Storable qw(dclone); say
"version=",$Storable​::VERSION; say
"limit=",$Storable​::recursion_limit; my @​tt; for (1..16000) { my $t =
[[[]]]; push @​tt, $t } dclone \@​tt'
version=3.08

Same problem also with the latest Storable, 3.11.

limit=15692
Max. recursion depth with nested structures exceeded at -e line 1.

(Depending on the calculated recursion_limit the number of iterations
has to be adjusted)

This is not a problem with earlier Storable versions, even with much
more
elements in this array​:

$ perl5.26.2 -E 'use Storable qw(dclone); say
"version=",$Storable​::VERSION; say
"limit=",$Storable​::recursion_limit; my @​tt; for (1..1_000_000) { my
$t = [[[]]]; push @​tt, $t } dclone \@​tt'
version=2.62
limit=
(no output)

If $Storable​::DEBUGME is set (and a debugging perl is available),
then one can see that recur_depth is constantly increasing.

BTW​: Storable's bug queue currently points to rt.perl.org. But
https://rt.cpan.org/Public/Dist/Display.html?Name=Storable still
exists and has a lot of open issues, and I find it much easier
to find Storable-related issues in a specific bug queue than in
the huge and rather unstructured perl bug queue. For example,
even if we have the flag "module=..." created by perlbug
it is nowhere reflected in rt.perl.org (e.g. it's not possible
to search for this field, it seems).

-----------------------------------------------------------------
---
Flags​:
category=library
severity=high
module=Storable
---
Site configuration information for perl 5.28.0​:

Configured by eserte at Sat Jun 23 08​:28​:02 CEST 2018.

Summary of my perl5 (revision 5 version 28 subversion 0)
configuration​:

Platform​:
osname=linux
osvers=3.16.0-4-amd64
archname=x86_64-linux
uname='linux cabulja 3.16.0-4-amd64 #1 smp debian 3.16.51-3 (2017-
12-13) x86_64 gnulinux '
config_args='-ds -e -Dprefix=/opt/perl-5.28.0
-Dcf_email=srezic@​cpan.org'
hint=recommended
useposix=true
d_sigaction=define
useithreads=undef
usemultiplicity=undef
use64bitint=define
use64bitall=define
uselongdouble=undef
usemymalloc=n
default_inc_excludes_dot=define
bincompat5005=undef
Compiler​:
cc='cc'
ccflags ='-fwrapv -fno-strict-aliasing -pipe -fstack-protector-
strong -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
-D_FORTIFY_SOURCE=2'
optimize='-O2'
cppflags='-fwrapv -fno-strict-aliasing -pipe -fstack-protector-
strong -I/usr/local/include'
ccversion=''
gccversion='4.9.2'
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/gcc/x86_64-linux-gnu/4.9/include-
fixed /usr/include/x86_64-linux-gnu /usr/lib /lib/x86_64-linux-gnu
/lib/../lib /usr/lib/x86_64-linux-gnu /usr/lib/../lib /lib
libs=-lpthread -lnsl -lgdbm -ldb -ldl -lm -lcrypt -lutil -lc
-lgdbm_compat
perllibs=-lpthread -lnsl -ldl -lm -lcrypt -lutil -lc
libc=libc-2.19.so
so=so
useshrplib=false
libperl=libperl.a
gnulibc_version='2.19'
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.28.0​:
/opt/perl-5.28.0/lib/site_perl/5.28.0/x86_64-linux
/opt/perl-5.28.0/lib/site_perl/5.28.0
/opt/perl-5.28.0/lib/5.28.0/x86_64-linux
/opt/perl-5.28.0/lib/5.28.0

---
Environment for perl 5.28.0​:
HOME=/home/eserte
LANG=en_US.UTF-8
LANGUAGE=en_US​:en
LD_LIBRARY_PATH (unset)
LOGDIR (unset)
PATH=/usr/local/bin​:/usr/bin​:/bin​:/usr/local/sbin​:/usr/sbin​:/sbin​:/home/eserte/bin/linux-
gnu​:/home/eserte/bin/sh​:/home/eserte/bin​:/home/eserte/bin/pistachio-
perl/bin​:/usr/games​:/home/eserte/devel
PERLDOC=-MPod​::Perldoc​::ToTextOverstrike
PERL_BADLANG (unset)
SHELL=/bin/zsh

@p5pRT
Copy link
Author

p5pRT commented Jul 3, 2018

From @eserte

Dana Tue, 03 Jul 2018 02​:03​:42 -0700, slaven@​rezic.de reče​:

Dana Tue, 03 Jul 2018 01​:40​:45 -0700, slaven@​rezic.de reče​:

This is a bug report for perl from slaven@​rezic.de,
generated with the help of perlbug 1.41 running under perl 5.28.0.

-----------------------------------------------------------------
The recursion check introduced in Storable 3.x croaks in cases
where there is no problematic recursion involved. A sample oneliner​:

$ perl5.28.0 -E 'use Storable qw(dclone); say
"version=",$Storable​::VERSION; say
"limit=",$Storable​::recursion_limit; my @​tt; for (1..16000) { my $t =
[[[]]]; push @​tt, $t } dclone \@​tt'
version=3.08

Same problem also with the latest Storable, 3.11.

On the other hand, it's still possible to bypass the recursion check, e.g. with the following oneliner​:

$ perl5.28.0 -E 'use Storable qw(dclone); say "version=", $Storable​::VERSION; $t = [{1,2},$t] for 1..30000; dclone $t'
version=3.08
[1] 16098 segmentation fault perl5.28.0 -E

Looking at Storable.xs I don't understand how cxt->recur_sv is supposed to work. Wouldn't it be more correct (and the code much simpler) if the depth variable is incremented on _every_ internal function call and decremented on every exit?

@p5pRT
Copy link
Author

p5pRT commented Aug 3, 2018

From @eserte

Dana Tue, 03 Jul 2018 01​:40​:45 -0700, slaven@​rezic.de reče​:

This is a bug report for perl from slaven@​rezic.de,
generated with the help of perlbug 1.41 running under perl 5.28.0.

-----------------------------------------------------------------
The recursion check introduced in Storable 3.x croaks in cases
where there is no problematic recursion involved. A sample oneliner​:

$ perl5.28.0 -E 'use Storable qw(dclone); say
"version=",$Storable​::VERSION; say
"limit=",$Storable​::recursion_limit; my @​tt; for (1..16000) { my $t =
[[[]]]; push @​tt, $t } dclone \@​tt'
version=3.08
limit=15692
Max. recursion depth with nested structures exceeded at -e line 1.

(Depending on the calculated recursion_limit the number of iterations
has to be adjusted)

This is not a problem with earlier Storable versions, even with much
more
elements in this array​:

$ perl5.26.2 -E 'use Storable qw(dclone); say
"version=",$Storable​::VERSION; say
"limit=",$Storable​::recursion_limit; my @​tt; for (1..1_000_000) { my
$t = [[[]]]; push @​tt, $t } dclone \@​tt'
version=2.62
limit=
(no output)

If $Storable​::DEBUGME is set (and a debugging perl is available),
then one can see that recur_depth is constantly increasing.

BTW​: Storable's bug queue currently points to rt.perl.org. But
https://rt.cpan.org/Public/Dist/Display.html?Name=Storable still
exists and has a lot of open issues, and I find it much easier
to find Storable-related issues in a specific bug queue than in
the huge and rather unstructured perl bug queue. For example,
even if we have the flag "module=..." created by perlbug
it is nowhere reflected in rt.perl.org (e.g. it's not possible
to search for this field, it seems).

-----------------------------------------------------------------
---
Flags​:
category=library
severity=high
module=Storable
---
Site configuration information for perl 5.28.0​:

Configured by eserte at Sat Jun 23 08​:28​:02 CEST 2018.

Summary of my perl5 (revision 5 version 28 subversion 0)
configuration​:

Platform​:
osname=linux
osvers=3.16.0-4-amd64
archname=x86_64-linux
uname='linux cabulja 3.16.0-4-amd64 #1 smp debian 3.16.51-3 (2017-
12-13) x86_64 gnulinux '
config_args='-ds -e -Dprefix=/opt/perl-5.28.0
-Dcf_email=srezic@​cpan.org'
hint=recommended
useposix=true
d_sigaction=define
useithreads=undef
usemultiplicity=undef
use64bitint=define
use64bitall=define
uselongdouble=undef
usemymalloc=n
default_inc_excludes_dot=define
bincompat5005=undef
Compiler​:
cc='cc'
ccflags ='-fwrapv -fno-strict-aliasing -pipe -fstack-protector-
strong -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
-D_FORTIFY_SOURCE=2'
optimize='-O2'
cppflags='-fwrapv -fno-strict-aliasing -pipe -fstack-protector-
strong -I/usr/local/include'
ccversion=''
gccversion='4.9.2'
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/gcc/x86_64-linux-gnu/4.9/include-
fixed /usr/include/x86_64-linux-gnu /usr/lib /lib/x86_64-linux-gnu
/lib/../lib /usr/lib/x86_64-linux-gnu /usr/lib/../lib /lib
libs=-lpthread -lnsl -lgdbm -ldb -ldl -lm -lcrypt -lutil -lc
-lgdbm_compat
perllibs=-lpthread -lnsl -ldl -lm -lcrypt -lutil -lc
libc=libc-2.19.so
so=so
useshrplib=false
libperl=libperl.a
gnulibc_version='2.19'
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.28.0​:
/opt/perl-5.28.0/lib/site_perl/5.28.0/x86_64-linux
/opt/perl-5.28.0/lib/site_perl/5.28.0
/opt/perl-5.28.0/lib/5.28.0/x86_64-linux
/opt/perl-5.28.0/lib/5.28.0

---
Environment for perl 5.28.0​:
HOME=/home/eserte
LANG=en_US.UTF-8
LANGUAGE=en_US​:en
LD_LIBRARY_PATH (unset)
LOGDIR (unset)
PATH=/usr/local/bin​:/usr/bin​:/bin​:/usr/local/sbin​:/usr/sbin​:/sbin​:/home/eserte/bin/linux-
gnu​:/home/eserte/bin/sh​:/home/eserte/bin​:/home/eserte/bin/pistachio-
perl/bin​:/usr/games​:/home/eserte/devel
PERLDOC=-MPod​::Perldoc​::ToTextOverstrike
PERL_BADLANG (unset)
SHELL=/bin/zsh

Ping.

@p5pRT
Copy link
Author

p5pRT commented Aug 6, 2018

From @tonycoz

On Fri, 03 Aug 2018 08​:06​:35 -0700, slaven@​rezic.de wrote​:

Ping.

I'll take a look at this.

Tony

@p5pRT
Copy link
Author

p5pRT commented Aug 6, 2018

The RT System itself - Status changed from 'new' to 'open'

@p5pRT
Copy link
Author

p5pRT commented Aug 7, 2018

From @tonycoz

On Fri, 03 Aug 2018 08​:06​:35 -0700, slaven@​rezic.de wrote​:

Dana Tue, 03 Jul 2018 01​:40​:45 -0700, slaven@​rezic.de reče​:

The recursion check introduced in Storable 3.x croaks in cases
where there is no problematic recursion involved. A sample oneliner​:

$ perl5.28.0 -E 'use Storable qw(dclone); say
"version=",$Storable​::VERSION; say
"limit=",$Storable​::recursion_limit; my @​tt; for (1..16000) { my $t =
[[[]]]; push @​tt, $t } dclone \@​tt'
version=3.08
limit=15692
Max. recursion depth with nested structures exceeded at -e line 1.

(Depending on the calculated recursion_limit the number of iterations
has to be adjusted)

This is not a problem with earlier Storable versions, even with much
more
elements in this array​:

$ perl5.26.2 -E 'use Storable qw(dclone); say
"version=",$Storable​::VERSION; say
"limit=",$Storable​::recursion_limit; my @​tt; for (1..1_000_000) { my
$t = [[[]]]; push @​tt, $t } dclone \@​tt'
version=2.62
limit=
(no output)

If $Storable​::DEBUGME is set (and a debugging perl is available),
then one can see that recur_depth is constantly increasing.

Please try the attached.

Tony

@p5pRT
Copy link
Author

p5pRT commented Aug 7, 2018

From @tonycoz

0001-perl-133326-fix-and-clarify-handling-of-recurs_sv.patch
From eb7c3f5f968a14ca81758bfc49d795469834f013 Mon Sep 17 00:00:00 2001
From: Tony Cook <tony@develop-help.com>
Date: Tue, 7 Aug 2018 15:34:06 +1000
Subject: (perl #133326) fix and clarify handling of recurs_sv.

There were a few problems:

- the purpose of recur_sv wasn't clear, I believe I understand it
  now from looking at where recur_sv was actually being used.
  Frankly the logic of the code itself was hard to follow, apparently
  only counting a level if the recur_sv was equal to the current
  SV.

  Fixed by adding some documentation to recur_sv in the context
  structure.  The logic has been re-worked (see below) to hopefully
  make it more understandable.

- the conditional checks for inc/decrementing recur_depth didn't
  match between the beginnings and ends of the store_array() and
  store_hash() handlers didn't match, since recur_sv was both
  explicitly modified by those functions and implicitly modified
  in their recursive calls to process elements.

  Fixing by storing the starting value of cxt->recur_sv locally
  testing against that instead of against the value that might be
  modified recursively.

- the checks in store_ref(), store_array(), store_l?hash() were
  over complex, obscuring their purpose.

  Fixed by:
   - always count a recursion level in store_ref() and store the
     RV in recur_sv
   - only count a recursion level in the array/hash handlers if
     the SV didn't match.
   - skip the check against cxt->entry, if we're in this code
     we could be recursing, so we want to detect it.

- (after the other changes) the recursion checks in store_hash()/
  store_lhash() only checked the limit if the SV didn't match the
  recur_sv, which horribly broke things.

  Fixed by:
   - Now only make the depth increment conditional, and always
     check against the limit if one is set.
---
 dist/Storable/Storable.xs | 98 ++++++++++++++++++++++++++++++-----------------
 dist/Storable/t/recurse.t | 16 +++++++-
 2 files changed, 77 insertions(+), 37 deletions(-)

diff --git a/dist/Storable/Storable.xs b/dist/Storable/Storable.xs
index 6a90e24814..f6df32b121 100644
--- a/dist/Storable/Storable.xs
+++ b/dist/Storable/Storable.xs
@@ -418,6 +418,24 @@ typedef struct stcxt {
     SV *(**retrieve_vtbl)(pTHX_ struct stcxt *, const char *);	/* retrieve dispatch table */
     SV *prev;			/* contexts chained backwards in real recursion */
     SV *my_sv;			/* the blessed scalar who's SvPVX() I am */
+
+    /* recur_sv:
+
+       A hashref of hashrefs or arrayref of arrayrefs is actually a
+       chain of four SVs, eg for an array ref containing an array ref:
+
+         RV -> AV (element) -> RV -> AV
+
+       To make this depth appear natural from a perl level we only
+       want to count this as two levels, so store_ref() stores it's RV
+       into recur_sv and store_array()/store_hash() will only count
+       that level if the AV/HV *isn't* recur_sv.
+
+       We can't just have store_hash()/store_array() not count that
+       level, since it's possible for XS code to store an AV or HV
+       directly as an element (though perl code trying to access such
+       an object will generally croak.)
+     */
     SV *recur_sv;               /* check only one recursive SV */
     int in_retrieve_overloaded; /* performance hack for retrieving overloaded objects */
     int flags;			/* controls whether to bless or tie objects */
@@ -431,8 +449,13 @@ typedef struct stcxt {
 
 #define RECURSION_TOO_DEEP() \
     (cxt->max_recur_depth != -1 && ++cxt->recur_depth > cxt->max_recur_depth)
+
+/* There's cases where we need to check whether the hash recursion
+   limit has been reached without bumping the recursion levels, so the
+   hash check doesn't bump the depth.
+*/
 #define RECURSION_TOO_DEEP_HASH() \
-    (cxt->max_recur_depth_hash != -1 && ++cxt->recur_depth > cxt->max_recur_depth_hash)
+    (cxt->max_recur_depth_hash != -1 && cxt->recur_depth > cxt->max_recur_depth_hash)
 #define MAX_DEPTH_ERROR "Max. recursion depth with nested structures exceeded"
 
 static int storable_free(pTHX_ SV *sv, MAGIC* mg);
@@ -2360,21 +2383,20 @@ static int store_ref(pTHX_ stcxt_t *cxt, SV *sv)
     } else
         PUTMARK(is_weak ? SX_WEAKREF : SX_REF);
 
-    TRACEME(("recur_depth %" IVdf ", recur_sv (0x%" UVxf ")", cxt->recur_depth,
-             PTR2UV(cxt->recur_sv)));
-    if (cxt->entry && cxt->recur_sv == sv) {
-        if (RECURSION_TOO_DEEP()) {
+    cxt->recur_sv = sv;
+
+    TRACEME((">ref recur_depth %" IVdf ", recur_sv (0x%" UVxf ") max %" IVdf, cxt->recur_depth,
+             PTR2UV(cxt->recur_sv), cxt->max_recur_depth));
+    if (RECURSION_TOO_DEEP()) {
 #if PERL_VERSION < 15
-            cleanup_recursive_data(aTHX_ (SV*)sv);
+        cleanup_recursive_data(aTHX_ (SV*)sv);
 #endif
-            CROAK((MAX_DEPTH_ERROR));
-        }
+        CROAK((MAX_DEPTH_ERROR));
     }
-    cxt->recur_sv = sv;
 
     retval = store(aTHX_ cxt, sv);
-    if (cxt->entry && cxt->recur_sv == sv && cxt->recur_depth > 0) {
-        TRACEME(("recur_depth --%" IVdf, cxt->recur_depth));
+    if (cxt->max_recur_depth != -1 && cxt->recur_depth > 0) {
+        TRACEME(("<ref recur_depth --%" IVdf, cxt->recur_depth));
         --cxt->recur_depth;
     }
     return retval;
@@ -2635,6 +2657,7 @@ static int store_array(pTHX_ stcxt_t *cxt, AV *av)
     UV len = av_len(av) + 1;
     UV i;
     int ret;
+    SV *const recur_sv = cxt->recur_sv;
 
     TRACEME(("store_array (0x%" UVxf ")", PTR2UV(av)));
 
@@ -2659,9 +2682,9 @@ static int store_array(pTHX_ stcxt_t *cxt, AV *av)
         TRACEME(("size = %d", (int)l));
     }
 
-    TRACEME(("recur_depth %" IVdf ", recur_sv (0x%" UVxf ")", cxt->recur_depth,
-             PTR2UV(cxt->recur_sv)));
-    if (cxt->entry && cxt->recur_sv == (SV*)av) {
+    TRACEME((">array recur_depth %" IVdf ", recur_sv (0x%" UVxf ") max %" IVdf, cxt->recur_depth,
+             PTR2UV(cxt->recur_sv), cxt->max_recur_depth));
+    if (recur_sv != (SV*)av) {
         if (RECURSION_TOO_DEEP()) {
             /* with <= 5.14 it recurses in the cleanup also, needing 2x stack size */
 #if PERL_VERSION < 15
@@ -2670,7 +2693,6 @@ static int store_array(pTHX_ stcxt_t *cxt, AV *av)
             CROAK((MAX_DEPTH_ERROR));
         }
     }
-    cxt->recur_sv = (SV*)av;
 
     /*
      * Now store each item recursively.
@@ -2701,9 +2723,12 @@ static int store_array(pTHX_ stcxt_t *cxt, AV *av)
             return ret;
     }
 
-    if (cxt->entry && cxt->recur_sv == (SV*)av && cxt->recur_depth > 0) {
-        TRACEME(("recur_depth --%" IVdf, cxt->recur_depth));
-        --cxt->recur_depth;
+    if (recur_sv != (SV*)av) {
+        assert(cxt->max_recur_depth == -1 || cxt->recur_depth > 0);
+        if (cxt->max_recur_depth != -1 && cxt->recur_depth > 0) {
+            TRACEME(("<array recur_depth --%" IVdf, cxt->recur_depth));
+            --cxt->recur_depth;
+        }
     }
     TRACEME(("ok (array)"));
 
@@ -2766,6 +2791,7 @@ static int store_hash(pTHX_ stcxt_t *cxt, HV *hv)
 #endif
                          ) ? 1 : 0);
     unsigned char hash_flags = (SvREADONLY(hv) ? SHV_RESTRICTED : 0);
+    SV * const recur_sv = cxt->recur_sv;
 
     /* 
      * Signal hash by emitting SX_HASH, followed by the table length.
@@ -2817,17 +2843,17 @@ static int store_hash(pTHX_ stcxt_t *cxt, HV *hv)
         TRACEME(("size = %d, used = %d", (int)l, (int)HvUSEDKEYS(hv)));
     }
 
-    TRACEME(("recur_depth %" IVdf ", recur_sv (0x%" UVxf ")", cxt->recur_depth,
-             PTR2UV(cxt->recur_sv)));
-    if (cxt->entry && cxt->recur_sv == (SV*)hv) {
-        if (RECURSION_TOO_DEEP_HASH()) {
+    TRACEME((">hash recur_depth %" IVdf ", recur_sv (0x%" UVxf ") max %" IVdf, cxt->recur_depth,
+             PTR2UV(cxt->recur_sv), cxt->max_recur_depth_hash));
+    if (recur_sv != (SV*)hv && cxt->max_recur_depth_hash != -1) {
+        ++cxt->recur_depth;
+    }
+    if (RECURSION_TOO_DEEP_HASH()) {
 #if PERL_VERSION < 15
-            cleanup_recursive_data(aTHX_ (SV*)hv);
+        cleanup_recursive_data(aTHX_ (SV*)hv);
 #endif
-            CROAK((MAX_DEPTH_ERROR));
-        }
+        CROAK((MAX_DEPTH_ERROR));
     }
-    cxt->recur_sv = (SV*)hv;
 
     /*
      * Save possible iteration state via each() on that table.
@@ -3107,8 +3133,9 @@ static int store_hash(pTHX_ stcxt_t *cxt, HV *hv)
     TRACEME(("ok (hash 0x%" UVxf ")", PTR2UV(hv)));
 
  out:
-    if (cxt->entry && cxt->recur_sv == (SV*)hv && cxt->recur_depth > 0) {
-        TRACEME(("recur_depth --%" IVdf , cxt->recur_depth));
+    assert(cxt->max_recur_depth_hash != -1 && cxt->recur_depth > 0);
+    TRACEME(("<hash recur_depth --%" IVdf , cxt->recur_depth));
+    if (cxt->max_recur_depth_hash != -1 && recur_sv != (SV*)hv && cxt->recur_depth > 0) {
         --cxt->recur_depth;
     }
     HvRITER_set(hv, riter);		/* Restore hash iterator state */
@@ -3221,6 +3248,7 @@ static int store_lhash(pTHX_ stcxt_t *cxt, HV *hv, unsigned char hash_flags)
 #ifdef DEBUGME
     UV len = (UV)HvTOTALKEYS(hv);
 #endif
+    SV * const recur_sv = cxt->recur_sv;
     if (hash_flags) {
         TRACEME(("store_lhash (0x%" UVxf ") (flags %x)", PTR2UV(hv),
                  (int) hash_flags));
@@ -3231,15 +3259,15 @@ static int store_lhash(pTHX_ stcxt_t *cxt, HV *hv, unsigned char hash_flags)
 
     TRACEME(("recur_depth %" IVdf ", recur_sv (0x%" UVxf ")", cxt->recur_depth,
              PTR2UV(cxt->recur_sv)));
-    if (cxt->entry && cxt->recur_sv == (SV*)hv) {
-        if (RECURSION_TOO_DEEP_HASH()) {
+    if (recur_sv != (SV*)hv && cxt->max_recur_depth_hash != -1) {
+        ++cxt->recur_depth;
+    }
+    if (RECURSION_TOO_DEEP_HASH()) {
 #if PERL_VERSION < 15
-            cleanup_recursive_data(aTHX_ (SV*)hv);
+        cleanup_recursive_data(aTHX_ (SV*)hv);
 #endif
-            CROAK((MAX_DEPTH_ERROR));
-        }
+        CROAK((MAX_DEPTH_ERROR));
     }
-    cxt->recur_sv = (SV*)hv;
 
     array = HvARRAY(hv);
     for (i = 0; i <= (Size_t)HvMAX(hv); i++) {
@@ -3252,7 +3280,7 @@ static int store_lhash(pTHX_ stcxt_t *cxt, HV *hv, unsigned char hash_flags)
                 return ret;
         }
     }
-    if (cxt->entry && cxt->recur_sv == (SV*)hv && cxt->recur_depth > 0) {
+    if (recur_sv == (SV*)hv && cxt->max_recur_depth_hash != -1 && cxt->recur_depth > 0) {
         TRACEME(("recur_depth --%" IVdf, cxt->recur_depth));
         --cxt->recur_depth;
     }
diff --git a/dist/Storable/t/recurse.t b/dist/Storable/t/recurse.t
index fa8be0b374..63fde90fdf 100644
--- a/dist/Storable/t/recurse.t
+++ b/dist/Storable/t/recurse.t
@@ -20,7 +20,7 @@ use Storable qw(freeze thaw dclone);
 
 $Storable::flags = Storable::FLAGS_COMPAT;
 
-use Test::More tests => 38;
+use Test::More tests => 39;
 
 package OBJ_REAL;
 
@@ -364,5 +364,17 @@ else {
         dclone $t;
     };
     like $@, qr/Max\. recursion depth with nested structures exceeded/,
-      'Caught href stack overflow '.MAX_DEPTH*2;
+      'Caught href stack overflow '.MAX_DEPTH_HASH*2;
+}
+
+{
+    # perl #133326
+    my @tt;
+    #$Storable::DEBUGME=1;
+    for (1..16000) {
+        my $t = [[[]]];
+        push @tt, $t;
+    }
+    ok(eval { dclone \@tt; 1 },
+       "low depth structure shouldn't be treated as nested");
 }
-- 
2.11.0

@p5pRT
Copy link
Author

p5pRT commented Aug 27, 2018

From @tonycoz

On Mon, 06 Aug 2018 22​:58​:00 -0700, tonyc wrote​:

Please try the attached.

Applied as 120060c.

Tony

@p5pRT
Copy link
Author

p5pRT commented Aug 27, 2018

@tonycoz - Status changed from 'open' to 'pending release'

@p5pRT
Copy link
Author

p5pRT commented May 22, 2019

From @khwilliamson

Thank you for filing this report. You have helped make Perl better.

With the release today of Perl 5.30.0, this and 160 other issues have been
resolved.

Perl 5.30.0 may be downloaded via​:
https://metacpan.org/release/XSAWYERX/perl-5.30.0

If you find that the problem persists, feel free to reopen this ticket.

@p5pRT
Copy link
Author

p5pRT commented May 22, 2019

@khwilliamson - Status changed from 'pending release' to 'resolved'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant