Perl provides a simple mechanism for specifying subroutine argument types called prototypes. Prototypes do not affect functions defined using the &
character. Furthermore, the perlfunc manpage states:
Method calls are not influenced by prototypes either, because the function to be called is indeterminate at compile time, since the exact code called depends on inheritance.
Prototypes do not cause Perl to emit any warnings if a subroutine's invocation uses methods that don't match its prototype, not even if the -w
switch is used. They also can change function behavior, and consequently should not be used.
Noncompliant Code Example
This noncompliant code example demonstrates a function with prototypes. The function takes a string and a list and simply prints out the string, along with the list elements.
sub function ($@) { my ($item, @list) = @_; print "item is $item\n"; my $size = $#list + 1; print "List has $size elements\n"; foreach $element (@list) { print "list contains $element\n"; } } my @elements = ("Tom", "Dick", "Harry"); function( @elements);
However, this program generates the counterintuitive output:
item is 3 List has 0 elements
The problem arises from two issues. First, Perl constructs a single argument list from its arguments, and this includes flattening any arguments that are themselves lists. This is why Perl allows function()
to be invoked with one list argument, rather than two. Second, the function prototype imposes contexts on the arguments it gets: a single scalar context for the first variable, and a list context from the second variable. These contexts are invoked on the arguments actually provided, rather than on the argument list. In this case, the scalar context is applied to the @elements
list, which yields 3, the number of elements in the list. Then the list context is applied to no argument, since only one argument was specified, and it produces an empty list (with 0 elements).
Compliant Solution
This compliant solution omits the prototype:
sub function { my ($item, @list) = @_; print "item is $item\n"; my $size = $#list + 1; print "List has $size elements\n"; foreach $element (@list) { print "list contains $element\n"; } } my @elements = ("Tom", "Dick", "Harry"); function( @elements);
With no prototype, the first element in the list "Tom"
is assigned to $item
, and the $list
gets the remaining elements: ("Dick", "Harry")
.
item is Tom List has 2 elements list contains Dick list contains Harry
Risk Assessment
Subroutine prototypes do not provide compile-time type safety, and can cause surprising program behavior.
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
DCL00-PL | low | likely | low | P3 | L3 |
Automated Detection
Tool | Diagnostic |
---|---|
Perl::Critic | Subroutines::ProhibitSubroutinePrototypes |
Bibliography
[Conway 05] pg. 194 "Prototypes"
[CPAN] Elliot Shank, Perl-Critic-1.116 Subroutines::ProhibitSubroutinePrototypes
[Wall 2011] perlsub
01. Declarations and Initialization 01. Declarations and Initialization