The @ISA variable is a package variable that is used by all classes to indicate the class's parent (or parents). While this variable can be safely read to learn a class's inheritence hierarchy, it must not be modified at runtime [Conway 05].
Noncompliant Code Example (@ISA)
This noncompliant code example defines a base class and an object class with simple methods:
{
package Base;
sub new {
my $class = shift;
my $self = {}; # no parent
bless $self, $class;
print "new Base\n";
return $self;
};
sub base_value {return 1;}
}
{
package Derived;
our @ISA = qw(Base); # establishes inheritence
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_); # relies on established inheritence
print "new Derived\n";
return $self;
};
sub derived_value {return 2;}
}
BEGIN {
my $derived = Derived->new();
my $b = $derived->base_value();
my $d = $derived->derived_value();
print "base_value = $d\n";
print "derived_value = $d\n";
}
When run, we get a program error:
Can't locate object method "new" via package "Derived::SUPER" at ...
This error occurs because the BEGIN block is evaluated at the beginning of run time, before the @ISA statement can be evaluated. Therefore, when the Derived::new() constructor is invoked, the Derived class has an empty parents list, and therefore fails to invoke Base::new().
Compliant Solution (base)
This compliant solution uses the base module rather than directly modifying the @ISA variable.
# ... package Base is unchanged
{
package Derived;
use base qw(Base);
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_); # relies on established inheritence
print "new Derived\n";
return $self;
};
sub derived_value {return 2;}
}
# ... The rest of the code is unchanged
The base module establishes the inheritence hierarchy at parse time, before any runtime code, including the BEGIN block is evaluated. Therefore, when the Derived::new() constructor is invoked, Perl knows that Derived is an instance of Base, and the program produces the correct output:
new Base new Derived derived_value = 2 base_value = 1
Risk Assessment
Modifying class inheritence at runtime can introduce subtle bugs, and is usually a sign of poor design.
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
OBJ30-PL | low | unlikely | low | {*}P3 | L1 |
Automated Detection
Tool | Diagnostic |
|---|---|
Perl::Critic | ClassHierarchies::ProhibitExplicitISA |
Bibliography
[Conway 05] pg. 360 "Inheritance"
[CPAN] Shank, Elliot, Perl-Critic-1.116 ClassHierarchies::ProhibitExplicitISA
[CPAN] Dolan, Chris. base
02. Expressions EXP31-PL. Do not use the two-argument form of open()