Name

Sub::Private - Private subroutines and methods

Version

Version 0.07

Synopsis

package Foo;
use Sub::Private;

sub foo { return 42 }

sub bar :Private {
    return foo() + 1;
}

sub baz {
    return bar() + 1;
}

Description

Enforces strictly private access on subroutines. A subroutine decorated with :Private (or named in use Sub::Private qw(...) when in enforce mode) may only be called from within its defining package. Subclasses do not inherit access: private means this package only.

Two Enforcement Modes

Behaviour of Wrapped Subs (Enforce Mode)

Loading at Run Time

Packages that use Sub::Private may be loaded at run time (require, a plugin loader, a string eval), and Sub::Private itself may be loaded for the first time that way. The private subs are then wrapped (or removed, in namespace mode) when the enclosing scope, normally the rest of the file, has been compiled.

Bypass for Testing

Either condition alone (OR logic) disables all access checks in enforce mode:

$Sub::Private::BYPASS is the recommended form for new test code. The HARNESS_ACTIVE bypass can be disabled:

$Sub::Private::config{harness_bypass} = 0;

Configuration

use Sub::Private { mode => 'enforce' };  -- this package only (recommended)
$Sub::Private::config{mode}            -- 'namespace' (default) or 'enforce',
                                          for packages that chose no mode
$Sub::Private::config{harness_bypass}  -- 1 (default); set to 0 to test enforcement

Error Message Format (Enforce Mode)

bar() is a private subroutine of Foo and cannot be called from Bar

Public Interface

Import

use Sub::Private;                    # attribute form -- no arguments
use Sub::Private qw(_a _b _c);      # declarative form (enforce mode only)
use Sub::Private { mode => 'enforce' };              # mode for this package
use Sub::Private { mode => 'enforce' }, qw(_a _b);   # both

Purpose

Called automatically by use Sub::Private.

With no arguments: makes the :Private attribute globally available via UNIVERSAL. No other action is taken.

With a hash reference first: sets options for the calling package only. The one option is mode, 'namespace' or 'enforce'. It decides what happens to that package's :Private subs and whether it may use the declarative form, whatever $Sub::Private::config{mode} says. The global mode is not changed, so other packages are not affected. The mode applies to subs in the package that made the use call, so put the use line inside the package.

With one or more sub names: registers those named subs in the calling package for access-enforcement wrapping at CHECK time. If CHECK has already fired (for example the package is loaded with require at run time), the subs are wrapped as soon as the enclosing scope (normally the rest of the file) has been compiled, so the use line can still come before the subs it names. A direct Sub::Private->import(...) call at run time wraps immediately. Requires the package's mode (see above), or else $Sub::Private::config{mode}, to equal 'enforce'; croaks otherwise.

Arguments

Returns

The class name ('Sub::Private') as a plain string in all cases.

Side Effects

Example

BEGIN { $Sub::Private::config{mode} = 'enforce' }
package MyClass;
use Sub::Private qw(_helper _init);

sub new     { bless {}, shift }
sub _helper { ... }    # wrapped at CHECK time
sub _init   { ... }    # wrapped at CHECK time
sub run     { my $s = shift; $s->_helper; $s->_init }

Enforce mode for one package, leaving every other package alone:

package MyClass;
use Sub::Private { mode => 'enforce' };

sub new        { bless {}, shift }
sub _helper :Private { ... }    # wrapped at CHECK time
sub run        { (shift)->_helper }

Api Specification

Input
# No-argument form: always valid.
Sub::Private->import();

# Options, first in the list (optional):
{
    options => {
        type     => 'hashref',
        optional => 1,
        schema   => {
            mode => {
                type     => 'string',
                memberof => [ 'namespace', 'enforce' ],
                optional => 1,
            },
        },
    }
}

# Declarative form (enforce mode only):
{
    subs => {
        type     => 'array',
        optional => 1,
        element  => {
            type  => 'string',
            regex => qr/\A[_a-zA-Z]\w*\z/,
        },
    }
}
Output
{ type => 'string' }    # returns the class name 'Sub::Private'

Messages

Message                                              Meaning / Action
---------------------------------------------------  -----------------------------------------------
"Sub::Private->import: declarative form requires     use Sub::Private qw(...) was called while
 mode => 'enforce'"                                  the package's mode is not 'enforce'.  Use
                                                     use Sub::Private { mode => 'enforce' },
                                                     qw(...) instead.

"Sub::Private->import: unknown option 'KEY'"         The options hash has a key other than mode.

"Sub::Private: unknown mode 'MODE' -- use            The mode option, or $config{mode}, is not
 'namespace' or 'enforce'"                           one of the two modes.

"Sub::Private->import: 'NAME' is not a valid         The sub name failed the identifier regex.
 Perl identifier"                                    Check for typos, hyphens, leading digits,
                                                     undef, or reference values in the import list.

"Sub::Private: PKG::NAME is not defined"             The named sub was not found in the stash at
                                                     wrap time.  Define the sub before import()
                                                     runs, before CHECK fires, or (after CHECK)
                                                     before the end of the enclosing scope.
                                                     After CHECK, the location reported is the
                                                     "use Sub::Private" line.

Public Variables

$Bypass

Set to a true value to disable all access checks (enforce mode only). Use local in tests; see "Bypass for testing".

%Config

Module-level configuration hash. Supported keys:

Known Limitations

Dependencies

Perl 5.14 or later, Carp (core), Attribute::Handlers (core), B::Hooks::EndOfScope, Readonly, Params::Validate::Strict, Return::Set, namespace::clean, Sub::Identify.

See Also

Formal Specification

The following Z-notation schemas formally specify the CheckAccess operation and import.

CheckAccess

-- Type abbreviations
Package  == seq CHAR     -- a non-empty Perl package name string
SubName  == seq CHAR     -- a Perl identifier string

-- Private-access predicate (strictly owner only -- no isa expansion)
permitted : Package x Package -> BOOL
forall caller, owner : Package .
    permitted(caller, owner) <=> caller = owner

-- System state
+-Registry-------------------------------------------+
| private   : P (Package x SubName)                  |
| bypass    : BOOL                                    |
| config    : { mode : seq CHAR,                      |
|               harness_bypass : BOOL }               |
| pkg_mode  : Package -+> seq CHAR                   |
+----------------------------------------------------+

-- Initial state
+-InitRegistry---------------------------------------+
| Registry                                           |
|----------------------------------------------------|
| private   = {}                                     |
| bypass    = false                                  |
| config    = { mode |-> 'namespace',                 |
|               harness_bypass |-> true }             |
| pkg_mode  = {}                                     |
+----------------------------------------------------+

-- Bypass predicate
bypass_active(R) <=>
    R.bypass or (R.config.harness_bypass and HARNESS_ACTIVE)

-- Access check: no state change
+-CheckAccess----------------------------------------+
| Xi-Registry                                        |
| caller? : Package                                  |
| owner?  : Package                                  |
| name?   : SubName                                  |
| ok!     : BOOL                                     |
|----------------------------------------------------|
| (owner?, name?) in private                         |
| ok! <=> bypass_active or permitted(caller?, owner?)|
+----------------------------------------------------+

-- Violation (croak case):
--   not ok! =>
--   croak("name?()" ++ " is a private subroutine of " ++ owner?
--         ++ " and cannot be called from " ++ caller?)

-- Key difference from Sub::Protected:
--   permitted(caller, owner) <=> caller = owner   (identity only)
-- vs Sub::Protected:
--   permitted(caller, owner) <=> owner in anc(caller)   (ISA chain)

Import

-- Valid identifier predicate
valid_id : SubName -> BOOL
valid_id(n) <=> n =~ /\A[_a-zA-Z]\w*\z/

-- Compile phase: CHECK has run, and code is still being compiled
post_check  : BOOL
compiling   : BOOL       -- true while $^S is undefined

-- The mode that applies to a package's subs
mode_for(p) = pkg_mode(p)   if p in dom pkg_mode
            = config.mode   otherwise

-- Options (hashref first): sets the caller's mode only
+-ImportOptions-------------------------------------+
| Delta-Registry                                    |
| mode? : seq CHAR                                  |
|---------------------------------------------------|
| mode? in { 'namespace', 'enforce' }               |
| pkg_mode' = pkg_mode (+) { caller |-> mode? }     |
| config' = config                                  |
+---------------------------------------------------+

-- Pre-condition (declarative form)
+-ImportPre-----------------------------------------+
| mode_for(caller) = 'enforce'                      |
| forall n in subs . valid_id(n)                    |
+---------------------------------------------------+

-- Post-condition (before CHECK): queue for the CHECK block
+-ImportPost_PreCheck-------------------------------+
| not post_check                                    |
|---------------------------------------------------|
| pending' = pending ++ < (caller, n) | n in subs > |
+---------------------------------------------------+

-- Post-condition (after CHECK, at run time): wrap now
+-ImportPost_RunTime--------------------------------+
| post_check and not compiling                      |
| forall n in subs . defined(caller, n)             |
|---------------------------------------------------|
| forall n in subs .                                |
|   stash'(caller, n) = wrapper(caller, n)          |
+---------------------------------------------------+

-- Post-condition (after CHECK, while compiling): wrap at the
-- end of the enclosing scope S
+-ImportPost_Deferred-------------------------------+
| post_check and compiling                          |
|---------------------------------------------------|
| at end_of_scope(S) :                              |
|   (forall n in subs . defined(caller, n)) =>      |
|     forall n in subs .                            |
|       stash'(caller, n) = wrapper(caller, n)      |
|   (exists n in subs . not defined(caller, n)) =>  |
|     croak at the "use" line                       |
+---------------------------------------------------+

-- In every case, a sub that is not defined when wrapping is
-- attempted croaks:
--   "Sub::Private: " ++ caller ++ "::" ++ n ++ " is not defined"

Author

Original Author: Peter Makholm, <peter at makholm.net>

Current maintainer: Nigel Horne, <njh at nigelhorne.com>

Bugs

Please report any bugs or feature requests to bug-sub-private at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Sub-Private.

Support

perldoc Sub::Private

Copyright 2009 Peter Makholm, all rights reserved. Portions copyright 2024-2026 Nigel Horne.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.