Bug#940922: perl: iterating hash with "each", no write to hash but references got messed up

Leszek Dubiel leszek.dubiel at dubielvitrum.pl
Sun Sep 22 00:39:30 BST 2019


Package: perl
Version: 5.24.1-3+deb9u5
Severity: normal

I was trying to make program below as simple as possible, but I don't understand where is the bug introduced. 

The task is to make a copy of hash such that keys in new hash are made only out of alphanumerics and underscores "_". Hashes are nested. 

Here is the program: 

	#!/usr/bin/perl 

	use Data::Dumper; 

	sub hash_is_okey { 
		ref $_[0] eq "HASH" or return 0;  

		# for my $k (keys %{$_[0]}) { 
		# 	my $v = ${$_[0]}{$k}; 

		while (my ($k, $v) = each %{$_[0]}) { 
			$k =~ /\A[_[:alnum:]]+\z/ or return 0; 
			hash_ok_or_scalar($v) or return 0; 
		}
		return 1;
	}

	sub hash_ok_or_scalar { return hash_is_okey($_[0]) || ! ref $_[0]; }

	sub make_hash_with_good_keys { 
		my ($x) = $_[0]; 

		hash_ok_or_scalar($x) and return $x; 

		ref $x eq "HASH" or die; 

		# copy hash "%{$x}" to new hash "%h" but make names good 
		my %h; 
		for my $k (keys %$x) { 

			# copy "$k" to "$n", then make good name of "$n"
			my $n = $k =~ s/[^[:alnum:]]/_/gr; 

			$h{$n} = make_hash_with_good_keys($$x{$k}); 
		} 
		return \%h; 
	}

	my $original = { "top_level" => { "level down" => 123 } }; 
	my $with_good_keys = make_hash_with_good_keys($original); 

	print Dumper($original, $with_good_keys); 


and here is the result: 

	$VAR1 = {
		  'top_level' => {
				   'level down' => 123
				 }
		};
	$VAR2 = {
		  'top_level' => $VAR1->{'top_level'}
		};

In program hash "$with_good_keys" is made by:
* initializing new hash "%h", and then asigning to that hash new value
   or 
* plugging reference if "subhash" has good keys. 

But the result is bad, because "VAR2" has bad key "level down" -- contains space. 


If I change this part of program: 

		# for my $k (keys %{$_[0]}) { 
		# 	my $v = ${$_[0]}{$k}; 

		while (my ($k, $v) = each %{$_[0]}) { 

to look like this (uncomment "for...keys", comment "while...each"): 

		for my $k (keys %{$_[0]}) { 
			my $v = ${$_[0]}{$k}; 

		# while (my ($k, $v) = each %{$_[0]}) { 

then result is: 

	$VAR1 = {
		  'top_level' => {
				   'level down' => 123
				 }
		};
	$VAR2 = {
		  'top_level' => {
				   'level_down' => 123
				 }
		};

Now "$VAR2" is a copy with names modified as expected. 

This is extremely strange, because I don't write to hash I am iterating over with "while...each". I tried to
resolve this myself for many hours, but I failed. 



-- System Information:
Debian Release: 9.11
  APT prefers oldstable-updates
  APT policy: (500, 'oldstable-updates'), (500, 'oldstable')
Architecture: amd64 (x86_64)

Kernel: Linux 4.9.0-9-amd64 (SMP w/4 CPU cores)
Locale: LANG=pl_PL.UTF-8, LC_CTYPE=pl_PL.UTF-8 (charmap=UTF-8), LANGUAGE=pl_PL.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash
Init: systemd (via /run/systemd/system)

Versions of packages perl depends on:
ii  dpkg               1.18.25
ii  libperl5.24        5.24.1-3+deb9u5
ii  perl-base          5.24.1-3+deb9u5
ii  perl-modules-5.24  5.24.1-3+deb9u5

Versions of packages perl recommends:
ii  netbase  5.4
pn  rename   <none>

Versions of packages perl suggests:
pn  libterm-readline-gnu-perl | libterm-readline-perl-perl  <none>
pn  make                                                    <none>
pn  perl-doc                                                <none>

-- no debconf information




More information about the Perl-maintainers mailing list