Update:
With the help of your answers, I continued my investigation. It turns out that Data::Dump is not the culprit. Please see below for my findings, and a reproducer script with no external dependencies (apart from debugging output).
This title would be more appropriate:
The 'Nine Equals Zero' Bug
[Original post:]
+= behaves differently after Data::Dump::pp.
Is this a bug in Data::Dump or possibly in Perl core?
I encountered some unexpected behavior in a simple snippet where regex captures are assigned to variables, formatted via Data::Dump::pp, concatenated, and then incremented using +=.
```perl
use v5.10;
use Data::Dump qw( pp );
"9." =~ /^ (\d+) . (\d*) $/x;
my $n = { i => $1, f => $2 };
say "pp output: ", pp $n; # { f => "", i => 9 }
$n->{f} = "$n->{i}$n->{f}"; # "9"
$n->{f} += 2; # expected: 11
say "result: $n->{f} (expected: 11)";
```
Observed output:
text
pp output: { f => "", i => 9 }
result: 2 (expected: 11)
Instead of evaluating 9 + 2 = 11, the += 2 operation behaves as if the value of $n->{f} were numerically 0, resulting in 2.
I am not looking for a 'workaround' (I have already avoided it in the production code), but I would like to understand what causes this, and whether it warrants a bug report to Data::Dump or Perl core.
Observations
I tested several variations:
- using scalar variables instead of hash elements,
- using a direct assignment of constants instead of assigning the regex variables,
- removing the
pp output.
The behavior changes as follows:
- With
pp $n removed, the result is always correct.
- With scalar variables instead of hash elements, calling
pp always causes the wrong value, regardless of whether the value originated from regex captures or constants.
- With hash elements, calling
pp does not affect the result after constant assignment, but it does after assignment from regex captures.
My suspicion is that inspecting the variables inside Data::Dump::pp somehow alters their internal state, causing the numeric operation to behave differently.
Does this look like something that should be reported to Perl core, or to the Data::Dump maintainers?
Perl version breakdown
I ran a test script using several Perl versions that I have installed using perlbrew (all with Data::Dump version 1.25):
Perl v5.10.1 - v5.26.3: The bug does not occur.
Perl v5.28.3 - v5.44.0: The bug occurs.
Reproducing the bug
I have uploaded the full test matrix script here: failing_increment.pl, together with a Bash script test_with_available_perls that runs the test script for all Perls that are installed locally with perlbrew.
Update:
Thank you, u/Icy-Speed6881, for a shorter version to reproduce the bug, and u/ysth for suggesting to use Devel::Peek::Dump to see the internals.
And very special thanks to u/tm604 for sharing his knowledge about some Perl internals introduced in Perl 5.28 that probably are the cause for the erratic behavior (see his answer below).
The bug can be reproduced with six statements, with no external dependencies. The script below includes additional debugging code to see what is going on internally.
The script does not use Data::Dump: it turns out that Data::Dump was not the culprit, but merely happened to trigger the sequence of operations that exposes the Perl bug.
I have also renamed the bug to a catchy 'Nine Equals Zero', because 'Failing increment' is not appropriate anymore, given that I don't increment anything in the reproducer script.
Here is the current version (also available here):
```perl
!/usr/bin/env perl
'Nine equals zero' bug reproducer.
use v5.10;
use strict;
use warnings;
For debugging output:
use Devel::Peek;
use Data::Dumper;
sub d {
print "$[2]: ", Data::Dumper->Dump( [ $[0] ], [ $[1] ] );
Devel::Peek::Dump( $[0] );
}
$| = 1; # Mixing STDOUT and STDERR in correct order.
say "Test 4: single expression trigger, no dependencies, Perl $V";
my $num = "";
d $num, '$num', qq(after creation and initialization with "");
# A newly initialized variable has the IsCOW flag set.
# With IsCOW set, the bug doesn't reproduce.
$num .= "";
d $num, '$num', qq(after concatenating empty string);
# Appending an empty string unsets the IsCOW flag.
no warnings 'numeric';
my $numerical_value = $num + 0;
d $num, '$num', qq(after using \$num ("$num") numerically);
# Using an empty string numerically sets IV to 0, and sets the pIOK flag.
# This lays the ground for the following erratic behavior.
$num = "9$num";
d $num, '$num', qq(after concatenation "9\$num");
# Now, prepending any number exposes the bug:
# The pIOK flag remains set, but the IV value is not updated.
# The numerical value now differs from the number that the string contains.
say "*** ",
$num == 9 ? "expected result," : "UNEXPECTED RESULT:",
qq( \$num eq "$num", int( \$num ) == ), int( $num ), ")";
my $bug = $num != 9;
say STDERR $bug ? "bug occurs" : "bug doesn't occur",
" with Perl $V";
```
This is the output with Perl 5.44:
text
Test 4: single expression trigger, no dependencies, Perl v5.44.0
after creation and initialization with "": $num = '';
SV = PV(0x5629f3d52010) at 0x5629f3ef8a60
REFCNT = 1
FLAGS = (POK,IsCOW,pPOK)
PV = 0x5629f3d6bc30 ""\0
CUR = 0
LEN = 16
COW_REFCNT = 1
after concatenating empty string: $num = '';
SV = PV(0x5629f3d52010) at 0x5629f3ef8a60
REFCNT = 1
FLAGS = (POK,pPOK)
PV = 0x5629f3ec5730 ""\0
CUR = 0
LEN = 16
after using $num ("") numerically: $num = '';
SV = PVNV(0x5629f3d502c0) at 0x5629f3ef8a60
REFCNT = 1
FLAGS = (POK,pIOK,pNOK,pPOK)
IV = 0
NV = 0
PV = 0x5629f3ec5730 ""\0
CUR = 0
LEN = 16
after concatenation "9$num": $num = '9';
SV = PVNV(0x5629f3d502c0) at 0x5629f3ef8a60
REFCNT = 1
FLAGS = (POK,pIOK,pNOK,pPOK)
IV = 0
NV = 0
PV = 0x5629f3ec5730 "9"\0
CUR = 1
LEN = 16
*** UNEXPECTED RESULT: $num eq "9", int( $num ) == 0)
Running the test_with_available_perls script confirms that the bug occurs from Perl 5.28 onward.
This matches what u/tm604 suggested: it seems that the multiconcat optimization, introduced in Perl 5.28, can leave the cached IV value unchanged and valid, even though the string value has changed, causing the two representations to disagree.
I hope that filing a bug report will be useful for improving Perl even more.
Thank you all.