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 inheritance hierarchy, it must not be modified at runtime [Conway 2005].
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 inheritance
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_); # relies on established inheritance
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 = $b\n";
print "derived_value = $d\n";
}
When the code is 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 runtime, before the @ISA statement can be evaluated. Consequently, when the Derived::new() constructor is invoked, the Derived class has an empty parents list and therefore fails to invoke Base::new().
Compliant Solution (parent)
This compliant solution uses the parent module rather than directly modifying the @ISA variable.
# ... package Base is unchanged
{
package Derived;
use parent qw(Base);
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_); # relies on established inheritance
print "new Derived\n";
return $self;
};
sub derived_value {return 2;}
}
# ... The rest of the code is unchanged
The parent module establishes the inheritance hierarchy at parse time, before any runtime code, including the BEGIN block, is evaluated. 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 base_value = 1 derived_value = 2
Risk Assessment
Modifying class inheritance at runtime can introduce subtle bugs and is usually a sign of poor design.
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
OOP00-PL | Low | Unlikely | Low | P3 | L3 |
Automated Detection
Tool | Diagnostic |
|---|---|
Perl::Critic | ClassHierarchies::ProhibitExplicitISA |
PERL_S47 |
Bibliography
| [Conway 2005] | "Inheritance," p. 360 |
| [CPAN] | Maischein, Max, parent |
| [CPAN] | Shank, Elliot, Perl-Critic-1.116 ClassHierarchies::ProhibitExplicitISA |



3 Comments
David Svoboda
Via email, Michael Greb says:
> OOP00-PL
>
> Use of base is discouraged in favor of parent, even base's own docs suggest this:
>> Unless you are using the fields pragma, consider this module
>> discouraged
> in
> favor of the lighter-weight parent.
> https://metacpan.org/module/base
>
> I believe use of fields, the only time someone would want base over parent, is also strongly discouraged by the Perl Community now a days.
You're right. I've updated the compliant solution to use parent instead of base. And I've added base to EXP31-PL as a deprecated module.
Anonymous
This doesn't say (nor do I think it should) that you can't modify @ISA directly at BEGIN time, which would also avoid the problem.
{BEGIN{ our @ISA = qw'Base' }Also there is a bug in the provided compliant solution.
If the packages are in the same file (as it appears in the non-compliant code) then you have to use:
use parent -norequire, qw'Base'There is also another bug
print"base_value = $d\n";should be
print"base_value = $b\n";David Svoboda
Agreed.
Well, the CS works fine on my OSX box (running perl 5.16). So "-norequire," is er...not required :)
Fixed, thanks!