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