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:
Code Block | ||||
---|---|---|---|---|
| ||||
{
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:
Code Block |
---|
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.
Code Block | ||||
---|---|---|---|---|
| ||||
# ... 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:
Code Block |
---|
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
[CPAN] Garcia-Suarez, Rafaël, Lateur, Bart, Maischein, Max, Siegel, Anno, Schwern, Michael parent
...
02. Expressions EXP31-PL. Do not use the two-argument form of open()