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 FIO30-PL. Do not use the two-argument form of open()