Search code examples
perloptional-parameterssubroutine

Where does a Perl subroutine get values missing from the actual parameters?


I came across the following Perl subroutine get_billable_pages while chasing a bug. It takes 12 arguments.

sub get_billable_pages {
    my ($dbc,
        $bill_pages,      $page_count,      $cover_page_count,
        $domain_det_page, $bill_cover_page, $virtual_page_billing,
        $job,             $bsj,             $xqn,
        $direction,       $attempt,
    ) = @_;

    my $billable_pages = 0;

    if ($virtual_page_billing) {
        my @row;
        ### Below is testing on the existence of the 11th and 12th parameters ###
        if ( length($direction) && length($attempt) ) {
            $dbc->xdb_execute("
                SELECT convert(int, value)
                FROM   job_attribute_detail_atmp_tbl
                WHERE  job             = $job
                AND    billing_sub_job = $bsj
                AND    xqn             = $xqn
                AND    direction       = '$direction'
                AND    attempt         = $attempt
                AND    attribute       = 1
            ");
        }
        else {
            $dbc->xdb_execute("
                SELECT convert(int, value)
                FROM job_attribute_detail_tbl
                WHERE job             = $job
                AND   billing_sub_job = $bsj
                AND   xqn             = $xqn
                AND   attribute       = 1
            ");
        }
        $cnt = 0;
        ...;

But is sometimes called with only 10 arguments

$tmp_det = get_billable_pages(
    $dbc2,
    $row[6],          $row[8],          $row[7],
    $domain_det_page, $bill_cover_page, $virtual_page_billing,
    $job1,            $bsj1,            $row[3],
);

The function does a check on the 11th and 12th arguments.

  • What are the 11th and 12th arguments when the function is passed only 10 arguments?

  • Is it a bug to call the function with only 10 arguments because the 11th and 12th arguments end up being random values?

  • I am thinking this may be the source of the bug because the 12th argument had a funky value when the program failed.

  • I did not see another definition of the function which takes only 10 arguments.


Solution

  • The values are copied out of the parameter array @_ to the list of scalar variables.

    If the array is shorter than the list, then the excess variables are set to undef. If the array is longer than the list, then excess array elements are ignored.

    Note that the original array @_ is unmodified by the assignment. No values are created or lost, so it remains the definitive source of the actual parameters passed when the subroutine is called.

    ikegami suggested that I should provide some Perl code to demonstrate the assignment of arrays to lists of scalars. Here is that Perl code, based mostly on his edit

    use strict;
    use warnings;
    
    use Data::Dumper;
    
    my $x = 44;             # Make sure that we
    my $y = 55;             # know if they change
    
    my @params = (8);       # Make a dummy parameter array with only one value
    
    ($x, $y) = @params;     # Copy as if this is were a subroutine
    
    print Dumper $x, $y;    # Let's see our parameters
    print Dumper \@params;  # And how the parameter array looks
    

    output

    $VAR1 = 8;
    $VAR2 = undef;
    
    $VAR1 = [ 8 ];
    

    So both $x and $y are modified, but if there are insufficient values in the array then undef is used instead. It is as if the source array was extended indefinitely with undef elements.

    Now let's look at the logic of the Perl code. undef evaluates as false for the purposes of conditional tests, but you apply the length operator like this

    if ( length($direction) && length($attempt) ) { ... }
    

    If you have use warnings in place as you should, Perl would normally produce a Use of uninitialized value warning. However length is unusual in that, if you ask for the length of an undef value (and you are running version 12 or later of Perl 5) it will just return undef instead of warning you.

    Regarding "I did not see another definition of the function which takes only 10 arguments", Perl doesn't have function templates like C++ and Java - it is up to the code in the subroutine to look at what it has been passed and behave accordingly.