Revision history for Class::Simple::Readonly::Cached
0.13 Sun Aug 9 07:57:25 PM EDT 2026
[ Bug Fixes ]
- Bump minimum Params::Get
Fixes https://github.com/nigelhorne/Class-Simple-Readonly-Cached/issues/12
- @ISA is now intentionally empty; was ('Class::Simple'), which caused
Class::Simple's AUTOLOAD to install permanent method globs that bypassed
the caching AUTOLOAD, silently breaking caching for any accessor that had
previously been called on a bare Class::Simple instance
- Use Carp::croak()/Carp::carp() fully-qualified (not imported aliases) so
that Test::Carp's runtime glob overrides intercept them correctly
- Fix DESTROY to delete $cached{$self->{object}} on wrapper teardown,
preventing a reference leak in long-running processes; also escape the
class name with \Q\E in the hash-key grep pattern
- Fix can() and isa() to guard against freed inner object during global
destruction ($self->{object} may no longer be a ref)
- Resolve FIXME in new(): strip 'cache' and 'quiet' from %inner before
passing remaining params to Class::Simple->new(), preventing those
wrapper-only keys from leaking into the inner object's attribute hash
[ Enhancements ]
- Added test dashboard
- Extract _cache_get, _cache_set, and _can_fixate as Sub::Private private
helpers; removes all bare die $key calls from AUTOLOAD
- Add Readonly constants $UNDEF_SENTINEL and $CHI_NEVER to replace magic
strings scattered through the code
- Add t/locales.t: geographic tests via Locale::Country::Multilingual
(concurrent instances, case-sensitive key storage, French lookups) and
POSIX locale tests verifying croak messages are locale-independent
- Full POD for all public methods: API SPECIFICATION, MESSAGES, FORMAL
SPECIFICATION, PSEUDOCODE (new), and EXAMPLE sections; add LIMITATIONS
- Add runtime dependencies: List::Util >= 1.33, Readonly, Scalar::Util,
Sub::Private
- Replace _cache_get/_cache_set private subs with _build_cache_accessors:
backend-specific _get/_set coderefs are now created once at construction
time, eliminating a Sub::Private caller() stack check plus a ref($cache)
call on every AUTOLOAD dispatch (significant for the HASH backend)
- Pre-compute _class = ref($self) in new(); key construction in AUTOLOAD
no longer calls ref($self) on every dispatch
- Gate grep+join in key construction behind "if @_"; zero-argument getter
calls (the most common pattern) now skip the temporary-list allocation
entirely
- Hoist wantarray to $wantlist at AUTOLOAD entry; was called 3-4 times
per miss path
- Replace map/grep/keys loop for %inner filtering in new() with a hash
copy plus delete-slice: one allocation, two O(1) deletes
- Use destructuring assignment for caller(0) in new(); avoids constructing
a 10-element @caller array when only fields 1 and 2 are needed
- Refactor can() from a nested ternary chain to explicit guard clauses with
syllogistic premise/conclusion comments
- Refactor isa() to guard-clause style; document that $class eq ref($self)
is an intentional O(1) fast path, not dead code redundant with SUPER::isa
- Add premise/conclusion comments to all three AUTOLOAD cache-hit exits and
both miss-path branches to make the mutual-exclusion invariant explicit
- Extend new() FORMAL SPECIFICATION with post-construction invariants:
W._class, W._cache_is_hash, W._get, W._set; add clone corollary
- Update AUTOLOAD POD to document the three mutually exclusive cached-value
forms (ARRAY ref / UNDEF_SENTINEL / plain scalar) and the scalar-then-list
fall-through behaviour
- t/carp.t: add cache=>[] (ARRAY ref) equivalence-partition test; proves the
cache-validation guard rejects all non-HASH refs, not only scalar refs
- t/lcm.t: load Class::Simple::Readonly::Cached at compile time to prevent
Sub::Private's CHECK block from firing too late under prove
- Add t/unit.t: 28-subtest black-box unit suite driven strictly by the POD
API; uses Test::Mockingbird (spy/mock), Test::Returns (returns_is), and a
ledger hash that tracks every documented message and return state, asserting
the ledger is empty at the end; inline UnitTest::Inner, UnitTest::BadCacheObj,
and UnitTest::ChiLike packages give full control over inner-object and
cache-backend behaviour; spy on UnitTest::Inner::scalar_val verifies inner
method is called exactly once on miss and zero additional times on hit;
spy on UnitTest::ChiLike::purge verifies purge() is called on CHI DESTROY
- Add t/function.t: 29-subtest white-box suite for internal helpers and
data-structure invariants; directly calls _can_fixate (all 6 equivalence
partitions: empty/plain/unblessed-ref/blessed/GLOB/$_ preservation) and
_build_cache_accessors (hash and CHI backends: field types, read/write
correctness, 'never' expiry); uses Test::Memory::Cycle to confirm closures
capture $c not $self (no circular reference); verifies exact cache key byte
format for all arg combinations; tests DESTROY prefix filtering and
undef-cache guard; tests state() live-ref semantics; uses spy on
Data::Reuse::fixate to verify fixate is called for safe lists and skipped
for lists containing blessed objects
- Add t/integration.t: 24-subtest black-box end-to-end suite; covers
multi-instance hash isolation, shared-backend key-collision documentation,
state tracking, argument-differentiated keys, scalar/list context mismatch,
undef sentinel, stale-cache limitation, DESTROY lifecycle (local %cached
inside inner scope ensures GC fires synchronously), DESTROY with shared hash
(all class-prefix keys cleared), clone semantics (shared vs. overridden
cache), object() bypass, can()/isa() delegation through inner class
hierarchy, double-wrap quiet/warning, duck-typed CHI backend, CHI DESTROY
purge(), no-object auto-creation, Data::Reuse::fixate spy on miss vs. hit,
CHI-independence proof via @INC blocker, and real CHI::Memory lifecycle
- Add t/extended_tests.t: 19-subtest suite targeting previously uncovered
execution paths to maximise LCSAJ/TER3 branch coverage; covers: can()
!ref($self->{object}) freed-inner fallback; class-level can() for state/
can/isa; isa() class-level !!() short-circuit; isa() freed-inner !!()
path; DESTROY skip-registry-delete guard; DESTROY stats preservation;
new() extra-kwarg forwarding and cache/quiet filtering; list-(undef) hash
backend stored as [undef] vs SENTINEL; scalar-context ARRAY hit returning
[-1]; miss count = 2 after scalar-then-list fallthrough; ARRAY overwrite
of scalar entry; post-DESTROY re-usability; no-arg clone // {} fallback;
non-cache clone param skips _build_cache_accessors; empty-list SENTINEL
storage; can()/isa() multi-level hierarchy delegation
- Add t/mutant_killers.t: 6-subtest mutation-test suite killing all 4 MEDIUM
and 3 LOW survivors from xt/mutant_20260809_032457.t; covers COND_INV_214_3
(clone cache-override writes to new cache, not old), BOOL_NEGATE_243_4 /
RETURN_UNDEF_243_4 (double-wrap returns original wrapper, not !ref or undef),
BOOL_NEGATE_397_2 / RETURN_UNDEF_397_2 (can("new") returns exact CODE ref),
BOOL_NEGATE_500_2 / RETURN_UNDEF_500_2 (_can_fixate truthy for safe lists,
falsy for blessed/GLOB, and integration spy confirms fixate is called)
- Add t/edge_cases.t: 32-subtest destructive/boundary/security suite; covers
coderef/GLOB/partial-CHI cache rejection, circular-reference inner object
acceptance, empty-string arg key collision (documented limitation), "0" arg
key distinctness, 64 KiB arg handling, overloaded-stringify arg in cache
key, string/hash-ref/blessed exception propagation through AUTOLOAD,
no-cache-on-exception invariant, $_ regression guard (scalar/list/hit-path),
falsy-but-defined returns (0/""/0), CHI get()/set() die propagation,
can(undef)/isa(undef)/isa("UNIVERSAL") hostile args, void-context dispatch,
UNDEF_SENTINEL spoofing (TODO: known bug), 1000-entry cache-flooding stress
test, and blessed-object-in-list skips fixate (RT#100461)
[ Security ]
- Replace all string eval() with block eval{} in test files (t/noplan.t,
t/pod-cm.t, t/pod-spelling.t, t/noopentickets.t): string eval runs the
full Perl parser and becomes a code-injection vector if a variable is ever
interpolated; block eval is strictly equivalent for fixed module loads
- Replace hardcoded /tmp/my-cache path in new() POD EXAMPLE with
File::Temp::tempdir(CLEANUP=>1): predictable /tmp paths are vulnerable to
symlink attacks and world-readable by default; File::Temp generates a
mode-0700 directory with a random suffix
- Add t/cgi_security.t: 18-subtest CGI penetration suite simulating hostile
HTTP inputs (XSS, CRLF injection, shell metacharacters, null bytes, path
traversal, cache flooding, oversized args, invalid constructor params);
confirms CSRC is transparent/safe for vectors it doesn't own and documents
two undocumented bugs as TODO: UNDEF_SENTINEL spoofing (inner object
returning the sentinel string is silently coerced to undef on cache hit)
and clone stats bleed (_hits/_misses are shallow-copied so concurrent
request-scoped clones share stat hashes under mod_perl/Plack)
0.12 Fri Jun 27 07:23:32 EDT 2025
Use Data::Reuse, but note RT#100461
Cache routines that return undef
0.11 Sun Apr 6 08:47:56 EDT 2025
Use gtar to create a distribution on Macs
Use Test::DescribeMe to simplify tests
Support CircleCI
Added t/30-basics.t
Better argument testing to new()
Fix isa() to work properly on subclasses
Keep class name in the key to keep purges safer
Use Params::Get
0.10 Sat Jun 22 10:22:42 EDT 2024
Added can() and isa() methods
Commented out what I believe is legacy code
No need to deref $rc twice
Calling new on an object now returns a clone rather than setting the defaults in the new object
0.09 Mon Mar 13 18:43:13 EDT 2023
Warn if we're caching an object that's already cached, then return
the previously cached object
0.08 Sat Apr 30 15:03:33 EDT 2022
Warn if $object is a scalar in new()
Warn if you're trying to cache an object that's already being cached
When a function returns an object, cache that object (TODO)
0.07 Fri Jan 8 18:52:37 EST 2021
Improved handling of return values including undef
0.06 Thu Dec 17 14:38:12 EST 2020
Was falsely claiming retrieval of an array after a scalar retrieval
is a cache hit
0.05 Thu 17 Oct 09:36:27 EDT 2019
Improved caching of functions that return 0
Better testing that caching actually caches everywhere
Added stats() call
0.04 Fri 11 Oct 19:38:38 EDT 2019
Object shouldn't be instantiated if cache argument is not given
Improved handling of scalar then array call on same method
0.03 Sun Sep 29 05:12:19 PDT 2019
Added the object() method
0.02 Fri Sep 27 03:43:56 PDT 2019
Added hash mode
0.01 Sun 22 Sep 10:01:14 EDT 2019
First Version