Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

ChrisDolan (2855)

ChrisDolan
  (email not shown publicly)
http://www.chrisdolan.net/

Journal of ChrisDolan (2855)

Friday October 06, 2006
11:38 PM

Class::Encapsulate - catch naughty code

[ #31245 ]

Earlier today, Curtis "Ovid" Poe wrote in his journal about a trick to restrict accesses to a blessed hash to packages in that instance's inheritence tree. He saw this as a clever way to add strict encapsulation to objects.

I got intrigued by this idea not for real code but for detecting naughty code during debugging. It overloads hash accesses to throw exceptions if anyone tries to access object properties directly instead of through accessor methods.

Even more interesting, I figured out how to insert the encapsulation at runtime!

I created a prototype (not headed to CPAN yet) that I'm calling Class::Encapsulate for now. Feedback on that name is welcome. As proof of concept, I applied this prototype to the Perl::Critic test suite. Here's the block of code I inserted into the Perl::Critic test code in t/40_criticize.t:


# Strict object testing -- prevent direct hash key access
eval { require Class::Encapsulate::Runtime; };
if ( !$EVAL_ERROR ) {
        for my $pkg ( '', '::Config', '::Policy', '::Violation' ) {
                diag 'apply Class::Encapsulate to Perl::Critic'.$pkg;
                Class::Encapsulate::Runtime->apply_to('Perl::Critic'.$pkg);
        }
}

My pre-0.01 prototype is in the Clotho SVN

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • I think I MUCH prefer testing for this your way than enforcing the encapsulation via all that extra code the inside-out way.
  • ref() lies about object blessings.

    my $pkg = ref $self; -> my $pkg = blessed $self;

    Only undef indicates "not-blessed." Some classes are false.

    if ( !$pkg ) { -> if ( !defined $pkg ) {

    You might want to look at ->DOES( ... ) as well. It's new to 5.10 but it gets at the idea of interfaces that don't use ISA. Allomorphism and all that stuff chromatic is on about.

    The return result of _hash_overload should probably be the object itself. You don't need to have a separate hash in %instances, store t

    • In fact he needs to duplicate all the things that Class::InsideOut [cpan.org] provides in order to be leak- and thread-safe.

      • Agreed. Which is why I don't want to bother making it super-safe. Josh's comments are much appreciated, and I'll implement as many of them as are easy, but I've made it clear in the POD that this is a hack. I do NOT recommend this for production code. Instead I intend to angle this specifically toward debugging.

        I've considered calling it Devel::Encapsulate. If you want real encapsulation, you should use a real implementation like inside-out objects.
      • It occurs to me that he can remove the DESTROY method along with the %instances hash and just throw errors when unauthorized access is attempted.

        No storage is even required to do this.
    • Excellent points. Thanks!

      The !$pkg part was just to catch a bug that I've since removed, so I think it's obsolete. ref() is good enough for this hack module because if you are using it with something other than blessed hashes, then you get what you deserve.
      • Ok? But ref() is still inaccurate. defined( blessed( ... ) ) is accurate and you already have Scalar::Util. Why not go for full fidelity?
        • Ok? But ref() is still inaccurate. defined( blessed( ... ) ) is accurate and you already have Scalar::Util. Why not go for full fidelity?


          I don't understand. ref() and blessed() only differ in behavior for SCALAR, HASH, ARRAY, CODE, etc. right? Are you worried that someone is going to apply Class::Encapsulate to HASH? Would that even work???
          • ref() returns a true value for all references even if they're not blessed. It returns a false value for objects blessed into the two classes 0 and ''.

            defined( blessed() is full fidelity because it always returns true for all objects and it always returns false for all non-objects. Plain bool( blessed() ) is *not* sufficient because not all class names evaluate to true.

            This is why blessed() returns undef instead of '' for it's "false" value. blessed( bless ..., 0 ) is false and so is blessed( bless ..., "\0"
            • I understand the merits of blessed(), but they are not relevant here. I already *know* my object is blessed. It wouldn't get into my overload function if it wasn't. If you look at the current SVN code [clotho.com] you'll see that I'm not using ref() in boolean context any longer.

              I don't care about non-blessed refs because they can't be affected by "use overload". And "0" and "" are not valid names in "package" statements, so I don't care about those either -- people who bless instances into packages like that get wh
              • Ooooh. Yeah. $self is already an object so you only need the class name. Oops.

                As for being safe with defined(blessed(...)), I advocate that because there really is seriously weird code out there and pretending otherwise just gets us into accidents.

                Also, there's a related issue that no one has come up with a good method overridable method to ask an object what class it wants to pretend to be blessed into. Maybe Moose/MOP provides a nice name. The ref() function is absolutely evil to override while at least b
    • I implemented a bunch of these suggestions. Would you be willing to look at SVN again? Thanks much!