Revision history for Class::Simple::Cached
0.07 Thu Aug 6 22:00:57 EDT 2026
[ Security ]
- clone path: always recalculate _is_hash_cache and _cache_prefix after the
%merged hash is built, so caller-supplied internal-field args cannot override
the dispatch type flag (internal field injection vulnerability, CVE-class:
object property tampering via constructor argument pollution)
- AUTOLOAD getter: add !ref($rc) pre-check before UNDEF_SENTINEL eq comparison
to prevent a wrapped object returning a blessed value with overloaded eq from
triggering a false cached-undef result
- AUTOLOAD getter: apply same !ref() guard to array-element sentinel check
- t/coverage.t: replace eval "use Foo $version" (string eval with interpolated
variable) with eval { require Foo; Foo->VERSION($min) } (block eval) to
eliminate the code-injection surface if the constant is ever changed to a
runtime value
[ Performance ]
- AUTOLOAD: replace regex /::(\w+)\z/ with substr+rindex to avoid regex engine
startup on every method dispatch (~200 ns/call)
- new(): precompute _cache_prefix (= "ClassName:") and _is_hash_cache (Bool) so
AUTOLOAD and the cache helpers avoid ref() and string concat on the hot path
- DESTROY: read _cache_prefix directly instead of interpolating "$class:" per grep
iteration; use precomputed _is_hash_cache flag instead of ref($cache)
- use constant _GLOBAL_PHASE_AVAILABLE: evaluate Perl version check once at
compile time rather than on every DESTROY call
[ Refactoring ]
- Extract _cache_get() and _cache_set() private helpers to eliminate 4x repeated
hash-ref vs. CHI dispatch conditional in AUTOLOAD
- Mark _cache_get() and _cache_set() :Protected via Sub::Protected to enforce
private access at the package boundary
- Hoist $key and $object before the getter/setter branch in AUTOLOAD
- Consolidate array-setter undef path: remove duplicated _cache_set calls
- AUTOLOAD: replace /::(\w+)$/ with /::(\w+)\z/ — \z is absolute end-of-string,
unlike $ which matches before a trailing newline
[ Bug Fixes ]
- Fix t/carp.t failure: new() now calls Carp::croak (not Params::Get's Carp::confess) when called with no arguments
Fixes https://github.com/nigelhorne/Class-Simple-Cached/issues/7
- Fix array-setter undef path writing to $cache->{$param} instead of $cache->{$key} (missing class-prefix in key)
- Fix multi-arg detection: replace if($_[1]) truthiness check with scalar(@_) > 1
- Fix CHI scalar setter returning CHI->set() status instead of the stored value
- Remove dead AUTOLOAD DESTROY branch (redundant with explicit DESTROY sub)
- Replace die with Carp::croak for sentinel-collision error in AUTOLOAD
- Use constant UNDEF_SENTINEL instead of repeated __PACKAGE__ string concatenation
- Clone path did not validate a newly-supplied cache argument: $obj->new(cache =>
undef) created an object that crashed on first method call instead of croaking
at construction. Added the same validation block that the non-clone path uses.
- can(undef): the $method eq 'new' comparison emitted "uninitialized value"
warnings when method was undef. Added `return unless defined $method` guard.
- isa(undef): same root cause; added `return unless defined $class` guard.
[ Tests ]
- Expand t/30-basics.t: undef caching, sentinel storage, array-setter key correctness, clone behaviour
- Remove unused CHI import from t/carp.t
- t/manifest.t: replace capturing groups with non-capturing (?:...) in filter regex;
replace $ anchor with \z; modernise to Test::DescribeMe + Test::Needs
- t/unit.t: new black-box API contract test suite — 35 subtests proving every
documented message, return state, and side-effect from the POD; includes an
API ledger that asserts 100% coverage of documented states, global-state
integrity checks ($@, $_, alarm()), and Test::Mockingbird spy verification
of the Carp::carp signalling path; uses Test::Returns for return-type schema
validation and tests all four documented new() error messages, all getter
cache-hit branches, all cache-miss branches, all setter paths, and all isa()
and can() paths
- t/function.t: new white-box function test suite — 42 subtests covering
every code path in new() (8 paths), can(), isa(), DESTROY() (4 paths),
_cache_get() and _cache_set() (hash and CHI backends each), and AUTOLOAD
(all 5 cache-hit branches + 4 cache-miss branches + scalar and array setter
paths); uses Test::Mockingbird spies to observe Carp::carp and CHI method
calls, Test::Memory::Cycle to verify no circular references, and
Test::Returns for return-type schema checks
- Makefile.PL, cpanfile: add Test::Mockingbird and Test::Memory::Cycle to
TEST_REQUIRES (used by t/function.t)
- t/cgi_security.t: new adversarial penetration test suite — 9 subtests
covering constructor validation, clone-path field injection, overloaded-eq
sentinel bypass, array sentinel croak, resource-exhaustion (1 kB method name),
key namespace isolation, undef sentinel storage, and return-type schema checks
via Test::Returns
- t/edge_cases.t: new destructive/pathological/security test suite — 50 subtests
covering every hostile constructor input type (undef, 0, "", arrayref, coderef,
scalar ref, typeglob, each missing CHI method), internal field injection via
both the fresh-new and clone constructors (_is_hash_cache, _cache_prefix),
clone-path cache validation (regression for the three bugs below), falsy values
(0, "", "0") documented as never cached, sentinel-collision round-trip (two
calls: miss returns sentinel string, hit decodes to undef), array-sentinel
croak, setter with explicit undef arg, circular reference in cache, 1 MB value
round-trip, 10 KB method name, upstream inner-getter/inner-setter/CHI-get/
CHI-set failures, DESTROY with missing cache field / empty hash / 500 entries,
global state preservation ($_ / $@ / $! / alarm()), can(undef) and isa(undef)
regression, CSC-wrapping-CSC double-layer caching, stale cache after direct
inner mutation, void-context getter, and object=>falsy defaulting behavior
- t/integration.t: new end-to-end workflow test suite — 16 subtests covering
hash-ref and CHI-backend complete lifecycles (new/setter/getter/DESTROY),
multi-instance isolation (separate caches, no cross-pollution), shared
hash-ref cache namespace isolation between different subclasses, shared
hash-ref DESTROY impact when two same-class instances share a cache,
CHI global-purge LIMITATION (DESTROY purges all entries from a shared
CHI cache), clone workflow (shared cache reference, prefix reuse),
subclass key isolation (different prefix per concrete class), can()/isa()
delegation to inner object, optional-CHI simulation via
Test::Without::Module (proves CHI is a caller-supplied runtime dependency,
not a compile-time one), concurrent instances (five simultaneous objects
with independent caches), undef sentinel round-trip, array setter/getter
parity across both backends, and a backend-parity subtest that runs the
same scalar setter/getter workflow against both hash-ref and CHI and
asserts identical caller-visible outcomes
- Test::Mockingbird spies verify CHI set() receives the 'never' expiry token
and purge() is called exactly once on DESTROY
- t/logic.t: new formal equivalence-partition proof suite — 12 subtests
proving getter dispatch (G1 plain scalar, G3 arrayref, G4 blessed object),
setter arg-count partitions (S1 scalar, A1 array), CHI AND-conjunction
(missing get, set, or purge each independently croaks), falsy-scalar
boundary (0 is not cached per documented limitation), and the context
asymmetry between empty list context (never cached) vs scalar undef (cached)
- t/hash.t, t/chi.t: replace FIXME comments with formal explanation of the
context asymmetry (list-context empty vs scalar-context undef caching)
[ Dependencies ]
- Add Test::Without::Module to TEST_REQUIRES in Makefile.PL and cpanfile
(used by t/integration.t)
- Add Test::Returns and Readonly to TEST_REQUIRES in Makefile.PL and cpanfile (used by t/cgi_security.t)
[ Documentation ]
- Full POD rewrite: add EXAMPLE, API SPECIFICATION, MESSAGES, FORMAL SPECIFICATION, PSEUDOCODE per method
- Add LIMITATIONS section covering falsy-value caching, sentinel collision, shared-cache purge, and Memoize
[ Logic / Deductive Reductions ]
- AUTOLOAD cache-hit dispatch: replace second `if(ref($rc) eq 'ARRAY')` with
`elsif` — after the `if(!ref($rc))` block always returns, ref($rc) is
guaranteed truthy; the elsif encodes this mutual exclusivity explicitly
- new(): replace redundant `ref($params->{'cache'}) eq 'HASH'` in the final
branch with `$params->{'_is_hash_cache'}` (transitive reduction: value was
already computed and stored two statements earlier)
0.06 Sat Apr 5 08:20:10 EDT 2025
Use Test::DescribeMe to simplify tests
Use Test::Needs
Use gtar to create a distribution on Macs
Added t/30-basics.t
Better argument testing to new()
Handle https://github.com/Perl/perl5/issues/14673
Fix isa() to work properly on subclasses
Keep class name in the key to keep purges safer
Calling new on an object now returns a clone rather than setting the defaults in the new object
0.05 Mon Jul 29 21:29:15 EDT 2024
Commented out what I believe is legacy code
Added can() and isa() methods
0.04 Tue Mar 2 10:26:40 EST 2021
Fix "Can't use an undefined value as an ARRAY reference"
Allow cache to be a reference to a hash
0.03 Fri 20 Sep 13:19:59 EDT 2019
Improved handling of caching functions that return undef
Empty the cache when the object is destroyed
0.02 Thu 19 Sep 19:30:43 EDT 2019
Fix working with arrays
0.01 Wed 18 Sep 09:23:11 EDT 2019
First Version