
Perl's foreach loop will iterate over a list, assigning each value to $_
. But if another variable is provided, it assigns the list elements to that variable instead. According to the perlsyn manpage, the foreach loop may be invoked with the foreach
keyword or the for
keyword. The foreach loop always localizes its iteration variable, which means the iteration variable does not preserve its value after the loop terminates. This can lead to surprising results if not accounted for. Consequently, it is recommended that the variable in a foreach loop be prefixed with my
to make it explicit that the variable is private to the loop. And it is required that the variable not be read after the loop terminates.
Noncompliant Code Example
This noncompliant code example iterates through a list, stopping when it finds an even number.
my $value; my @list = (1, 2, 3); for $value (@list) { if ($value % 2 == 0) { last; } } print "$value is even\n";
However, the loop treats the iteration variable $value
as local. So when it exits the list, $value
regains the value it had before the loop. Because it was uninitialized before the loop, it remains undefined afterwards, and the final print
statement prints:
is even.
Compliant Solution (Expanded Loop)
This compliant solution correctly prints 2 is even
. It accomplishes this result by moving the print
statement inside the loop and never refers to $value
outside the loop.
my @list = (1, 2, 3); for my $value (@list) { if ($value % 2 == 0) { print "$value is even\n"; last; } }
Compliant Solution (External Variable)
This compliant solution preserves the value of $value
by assigning it to a lexical variable defined outside the loop. It still declares $v
to be private to the loop using my
.
my @list = (1, 2, 3); my $value; for my $v (@list) { if ($v % 2 == 0) { $value = $v; last; } } print "$value is still even\n";
Compliant Solution (for
)
This compliant solution uses the noniterative form of for
, which does no localization of its iteration variable.
my @list = (1, 2, 3); for ($value = 1; $value < 4; $value++) { if ($value % 2 == 0) { last; } } print "$value is still even\n";
Risk Assessment
Failure to localize iterators can lead to unexpected results.
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
DCL03-PL | low | unlikely | low | P3 | L3 |
Automated Detection
Tool | Diagnostic |
---|---|
Perl::Critic | Variables::RequireLexicalLoopIterators |
Bibliography
[Conway 2005] | "Iterator Variables," p. 105 |
[CPAN] | Elliot Shank, Perl-Critic-1.116 Variables::RequireLexicalLoopIterators |
[Wall 2011] | perlsyn |