From 95326d1a51541a825a808208b266a416fd658274 Mon Sep 17 00:00:00 2001 From: Toddr Bot Date: Wed, 22 Jul 2026 07:22:59 +0000 Subject: [PATCH] fix: weaken re-keyed child refs in rename to prevent mock leak When __rename re-keys directory children in %files_being_mocked, the new entries were stored as strong references. This prevented child mocks from being garbage collected when the user dropped their reference, causing the mock to persist and continue intercepting file operations indefinitely. Every other insertion point into %files_being_mocked already weakens the entry (new(), _new_link_for_broken_symlink(), _maybe_autovivify()); __rename was the sole omission. --- lib/Test/MockFile.pm | 1 + t/rename.t | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/Test/MockFile.pm b/lib/Test/MockFile.pm index 6c5d4cf..03eef33 100644 --- a/lib/Test/MockFile.pm +++ b/lib/Test/MockFile.pm @@ -4101,6 +4101,7 @@ sub __rename ($$) { delete $files_being_mocked{$key}; $files_being_mocked{$new_key} = $child; + Scalar::Util::weaken( $files_being_mocked{$new_key} ); $child->{'path'} = $new_key; # Update autovivify tracking for child directories diff --git a/t/rename.t b/t/rename.t index 439da47..bd07bc4 100644 --- a/t/rename.t +++ b/t/rename.t @@ -8,6 +8,7 @@ use Test2::Tools::Explain; use Test2::Plugin::NoWarnings; use Errno qw/ENOENT EISDIR ENOTDIR ENOTEMPTY/; +use Scalar::Util qw/isweak/; use Test::MockFile qw< nostrict >; @@ -227,6 +228,23 @@ note "-------------- rename: directory DESTROY cleanup works after rename ------ ok( -e '/mock/dtor2/f.txt', 'child accessible before DESTROY' ); } +note "-------------- rename: re-keyed children keep weak refs in files_being_mocked --------------"; +{ + my $dir = Test::MockFile->new_dir('/mock/weaktest'); + my $child = Test::MockFile->file( '/mock/weaktest/w.txt', 'data' ); + my $dest = Test::MockFile->dir('/mock/weaktest2'); + + ok( rename( '/mock/weaktest', '/mock/weaktest2' ), 'rename for weakref test' ); + + ok( isweak( $Test::MockFile::files_being_mocked{'/mock/weaktest2/w.txt'} ), + 'child entry in files_being_mocked is weakened after directory rename' ); + + # When user drops the reference, mock should be garbage collected + undef $child; + ok( !defined $Test::MockFile::files_being_mocked{'/mock/weaktest2/w.txt'}, + 'child mock is garbage collected after user drops reference' ); +} + note "-------------- rename: preserves inode and nlink --------------"; { my $old = Test::MockFile->file( '/mock/ino_old', 'data', { inode => 42, nlink => 3 } );