Perl provides a simple mechanism for specifying subroutine argument types called prototypes. Prototypes appear to indicate the number and types of arguments that a function takes. For instance, this function appears to require two arguments, the first being a scalar, and the second being a list:
sub function ($@) { # ... function body }
However, prototypes are problematic in many ways. The biggest problem is that prototypes are not enforced by Perl's parser. That is, prototypes do not cause Perl to emit any warnings if a prototyped subroutine is invoked with arguments that violate the prototype.. Perl does not issue any warnings of prototype violations, even if the -w
switch is used.
Prototypes suffer from several other problems, too. They can change function behavior, by forcing scalar context when evaluating arguments that might not be scalars, or by forcing list context when evaluating arguments that might not be lists. A function's prototype is ignored when that function is invoked with the &
character. Finally, according to the perlfunc manpage [Wall 2011]:
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.
Because of these problems, subroutine prototypes must not be used when defining subroutines.
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 my $element (@list) { print "list contains $element\n"; } } my @elements = ("Tom", "Dick", "Harry"); function( @elements);
However, this program generates the following 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 process includes flattening any arguments that are themselves lists. For this reason, 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 my $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 | P9 | L2 |
Automated Detection
Tool | Diagnostic |
---|---|
Perl::Critic | Subroutines::ProhibitSubroutinePrototypes |
Bibliography
[Conway 2005] | "Prototypes," p. 194 |
[CPAN] | Elliot Shank, Perl-Critic-1.116 Subroutines::ProhibitSubroutinePrototypes |
[Wall 2011] | perlsub |
4 Comments
Jeffrey Thalhammer
You mention this, but I think it is worth emphasizing that prototypes are risky because they probably don't behave the way you expect, not because they themselves are a security risk.
David Svoboda
Agreed, I've wordsmithed the intro to highlight this particular problem.
Anonymous
This sentence is wrong:
Prototypes do not affect functions defined using the
&
character.To correct it, just replace defined with called.
Which means that this will ignore a prototype associated with function.
&function(
@elements
);
Also it would be perfectly fine, if a bit dumb, to use a prototype of
(
@
)
. As it will work exactly the same as a function without a prototype.David Svoboda
Agreed, the
&
operator causes a function's prototype to be ignored; the function is invoked as if it had no prorotype. I reworded the intro to reflect this. So while the NCCE could be fixed by using&
, forbidding prototpyes is easier and safer.