develooper Front page | perl.perl5.porters | Postings from October 2007

Broken Perl OO-parent-method call

Thread Next
From:
Linda W
Date:
October 25, 2007 15:58
Subject:
Broken Perl OO-parent-method call
Message ID:
47211F7D.5020903@tlinx.org
I think I'm close to figuring out how my program created a situation
that seems to break Perl's OO, ancestor-method lookup machinery.

Taking the reference of a "non-package-local" method, corrupts
Perl's OO base-class, method lookup.  Note: the reference is not
used to call the method. Simply referencing it (even out of
"scope") generates the error.

The short description: Start with a derived class trying to call a
parent class method.  At some point in the derived class, create 'an
expression' (maybe it is assigned to a var or used to initialize an
array).  The expression is a reference to a method in the parent
class (i.e. "\&BaseMethod").  The expression may be printed, but is
otherwise unused and, _may_, immediately go out of scope.  After
that reference, a _LEGAL_ attempt to use that method fails with a
"<current_packagename::parent_method> not found" type message.

Once the parental method has been "disabled-by-reference", further
calls from the same package to that method also fail (even though
they may have previously worked).  This is also demonstrated in the
attached example in the package-local method, "disp_fields()".  It
is called, successfully, from 'main' shortly after object creation.
The same method fails when called from method "handle_key()" after
the reference.  I use "after" in the "time-sense", during execution;
not the syntactic placement in the source.

The attached program is mostly debugging code.  I've attempted to
make the code generate sufficient output to explicitly identify the
problem.  Only four external packages are used, but none are
required to duplicate the problem, only clarify the example.  The
modules referenced and their purpose for inclusion are:

    1)  "Carp" -- necessary to dump callback tracebacks in
        consistency checks

    2)  "Exporter" (used to by Debug code to export Debug routines)

    3)  "strict" (used to verify no use of catchable, proscribed
        problems)

    4)  "base" optional (preferred) replacement for explicit
        declaration of @ISA

The example code is attached.  An 'English' description of code and
problem follows.

Start with 2 classes (and a "main").  One is "ParentClass",
containing two identical datums: $_field_one and $_field_two. They
are initialized in "new()" and have read-only accessibility through
isomorphic accessor functions: field_one() and field_two() using the
same helper routine, "_accessor_helper".

The second class, "DerivedClass" has one datum, "$_field_three" and
also has read-only accessibility through accessor function
field_three().  It uses the same accessor function as field_one()
and field_two().

DerivedClass uses three of its parent's classes: field_one,
field_two and "_accessor_helper".

DerivedClass has a "handle_key" method, called on user-keyboard
input to demonstrate the problem.  Five keys are handled: 'q' -
quit; '1', '2' and '3' to call the corresponding accessor methods,
field_one(), field_two() and field_three(); and ' ' (<SpaceBar>)
that calls DerivedClass method, "disp_fields()", that displays the
results of all three data accessor functions.

While field_three() is located in DerivedClass, fields _one() and
_two() are both in "ParentClass".  Those two fields are accessed,
identically, in "DerivedClass".  The input keys '1' and '2' are
explicitly checked-for in an if/elsif clause.  Both keys use the
same anon subroutine, $check_if_can_then_call()":

    if ($key eq '1') {
        $check_if_can_then_call->('one');
    } elsif ($key eq '2') {
        $check_if_can_then_call->('two');
    } ...

$check_if_can_then_call() checks that the object "can" do requested
method, and if so, calls the method:

    my $check_if_can_call = sub {
        $meth = "string-based method name";
        if ($s->can($meth)  # if $s can do the method
            $s->$method;    # use Perl OO machinery to call it
    }; ...
    
N.B. This uses a string-based name for method access ( both in the
"can" statement and when calling it.


That's pretty much it.  Methods field_one() and field_two() are
isomorphic (identical except for their names).

Perl's OO calling mechanism breaks when trying to call 'field_two'.
The same object reference is printed for both, field_one() and
field_two().  Both are valid references to the same blessed hash, of
type "DerivedClass".  Perl "claims", in both cases, that the object
referred to, can() do the desired method, but in the case of
field_two(), this is not true and the program dies with the message:

    "Undefined subroutine &DerivedClass::field_two called at ..."

In addition to Perl's OO lookup mechanism failing, Perl's lookup of
"field_two()" is inconsistent (indeterminate).  In my experience, a
language translator producing undocumented, indeterminate output  is
usually a "bad" thing.  

I haven't been able to find any reference indicating language usage
illegality" (w.r.t. language definition and documentation).

As a further indicator of the problem, the value of can(), as used
on method field_two(), changes.  Although can() appears designed for
true/false testing, it appears to print the code-ref of the tested
method, if it is valid for the object reference.  If there is only
one method named "field_two()", I would expect it to have the same
code-ref value.

In "main", after the object is created with:

    my $s = DerivedClass->new();

the reference's classes and "can" methods are printed.  This is
followed by "$s->disp_fields".  Disp_fields() not only prints the
values of fields one, two and three, but also the code-refs of the
accessor functions.  I believe the code-ref for a valid, _specific_
class-method should remain constant for a given object-reference (at
least in the context of this problem).  

The code-refs for the accessors are also printed before they are
called in the single-accessor cases (when called singly).  The value
of field_three()'s code-ref stays the same (there is only one
field_three() accessor in the program).

The same is not true for field_two().  There is only one accessor
method named field_two() in the program, but the code-ref to it
changes from when it is called from main, to when it is called in
method $s->handle_key().  This would seem to be another "problem".
The code-ref to field_two() should be constant or "undef" (null) if
the method doesn't exist (i.e. 'can' would be False).  This isn't
the case.

The root of this bug seems to be that Perl is creating a 2nd (and
bogus) code-ref in method handle_key(), where the code exists to
print the value of "\&field_two".   However, after that reference
has gone out of scope, Perl saves it and confuses it with the real
"field_two".  Instead of calling the real field_two() method (which
exists and should work just like field_one()), which Perl says it
"can" do, it calls an unrelated, dummy, error routine instead of the
correct 'field_two().

While the usefulness of taking a reference to a non-existent local
method is questionable, the subsequent OO-call mechanism failure
should not be the result.

If I create a reference to a non-existent routine (function or
method) that is still non-existent at execution time, the reference
should return 'undef' when examined - not a pointer to a runtime
specified error-routine.

A 'valid' usage might be run-time program auto-configuration based
the existence of different sets of functions in included 'libraries'
(library modules).  A non-loaded routine should return an address of
'null' ('undef') when referenced.  This would be equivalent to a
run-time loader patching up a function call table, at run-time, and
filling in 'undefined' routines with 'null'.  A user program would
check the reference for validity (non-null) before trying to use it
in an indirect call.  If it isn't present, the program could alter
its behavior, dynamically at runtime.  Windows and IRIX have used
this functionality, though I've never seen it used (its not well
supported) in linux.

Comments?  Or "yeah it's a bug, just go ahead and submit it via
perbug"? :-)

L.


Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About