Skip to content

Filter @INC hook refs from yath startup and process_includes#449

Open
yuu-no wants to merge 1 commit into
Test-More:1.0from
yuu-no:fix/orig-inc-hook-refs
Open

Filter @INC hook refs from yath startup and process_includes#449
yuu-no wants to merge 1 commit into
Test-More:1.0from
yuu-no:fix/orig-inc-hook-refs

Conversation

@yuu-no
Copy link
Copy Markdown

@yuu-no yuu-no commented May 8, 2026

Summary

yath crashes (or silently corrupts child -I flags) when @INC contains a hook entry — a coderef, an arrayref, or a blessed object — as documented in perlvar "@INC". The most common trigger in the wild is Carmel, which injects a blessed @INC hook via PERL5OPT=-MCarmel::Setup, but anything that follows the hook protocol breaks the same way.

This PR filters hook refs out of the two places yath snapshots @INC:

  1. lib/App/Yath/Script/V1.pm — the early @ORIG_INC snapshot in App::Yath::Script::V1::call that flows into settings->harness->orig_inc.
  2. lib/Test2/Harness/Util.pmprocess_includes() when called with include_current => 1 (used from Runner.pm, runner.pm, and Job.pm).

Reproduction

$ cpanm Carmel
$ carmel exec -- yath test t/foo.t
encountered object 'Carmel::Runtime::FastINC=HASH(0x...)', but neither allow_blessed,
convert_blessed nor allow_tags settings are enabled (or TO_JSON/FREEZE method missing)

Without Carmel, any module that does unshift @INC, sub { ... } or unshift @INC, [\&handler, @state] exhibits the same problem: the JSON encode in write_settings_to fails on blessed hooks, and clean_path() (which uses realpath/rel2abs) stringifies coderef and arrayref hooks into bogus paths like CODE(0x55a...) / ARRAY(0x55b...), which then leak into child -I flags via process_includes.

Root cause

There are two independent code paths that snapshot @INC into something that must be a list of plain path strings:

  • App::Yath::Script::V1::call captures @INC early into @ORIG_INC and stores it on settings->harness->orig_inc. That setting is later JSON-encoded by write_settings_to, which throws on blessed objects, and stringified into -I arguments for child processes.
  • Test2::Harness::Util::process_includes, called with include_current => 1 from the runner, re-introduces the live @INC of the parent — including any hooks inherited from PERL5OPT — into the include list that is passed through clean_path and ultimately to child workers.

Filtering only the first site is not sufficient: the runner re-pulls hook refs from its own @INC and the same garbage paths reappear downstream.

Fix

In both sites, drop entries that are references:

my @ORIG_INC = grep { ref $_ eq '' } @INC;                                   # V1.pm
push @list => grep { ref $_ eq '' } @INC if delete $params{include_current}; # Util.pm

Hook refs cannot cross the exec boundary as -I arguments anyway (the child gets a string path from the kernel), so filtering them at the snapshot point loses no functionality — the child's own startup will redo whatever PERL5OPT injection the parent did.

Tests

t/integration/inc_hook.t runs yath test under three fixture modules that inject each of the hook forms documented in perlvar "@INC":

Fixture Hook form
FakeHook.pm blessed object
CoderefHook.pm coderef
ArrayrefHook.pm [\&handler, ...]

Each case asserts:

  • no encountered object JSON encode error in the output
  • no stringified ref (HASH(0x...) / CODE(0x...) / ARRAY(0x...)) leaked into a path

This is broader than the unit-level coverage proposed in #308 — it exercises both sites of the fix end-to-end through an actual child invocation.

Related issues / PRs

  • Closes Yath cannot be run through carmel exec #277 (Yath cannot be run through carmel exec). The original reporter closed the issue saying "no longer an issue with newest App::Yath", but on inspection that observation is based on the 2.0 branch where the affected files have been moved under reference/legacy/. On the 1.0 line — which this PR targets — the bug is still present and reproducible.
  • Supersedes Fix #277: filter blessed/ref entries from @INC #308 (closed without review). Fix #277: filter blessed/ref entries from @INC #308 took the same general approach (filter at the script entry + process_includes) but was a bot-generated PR with merge conflicts and no integration test, and was closed without engagement. This PR uses the same filter shape (functionally equivalent: ref $_ eq ''!ref($_)) and adds end-to-end coverage for all three hook forms.

Branch target

1.0. The corresponding files do not exist in this form on 2.0 (they have moved to reference/legacy/), so no port to 2.0 is needed.

Notes for review

  • The fix only filters references; non-ref @INC entries (the normal case) are untouched, so existing behavior is preserved.
  • No new dependencies. The fixture modules are pure-Perl with use strict; use warnings;.
  • t/integration/inc_hook.t is marked # HARNESS-DURATION-MEDIUM because it spawns three full yath test runs.

AI assistance disclosure (per AI_AND_LLM_POLICY.txt)

Drafting and code generation in this PR were assisted by Claude (Anthropic), noted via Co-Authored-By: trailer on the commit. The diagnosis (two-site snapshot, JSON encode crash, ref stringification through clean_path), the choice of filter shape, the test design covering all three hook forms, and the issue/PR archaeology against 1.0 vs 2.0 were vetted by a human reviewer. I can answer questions about every line of the change.

@yuu-no yuu-no marked this pull request as draft May 8, 2026 05:50
@yuu-no yuu-no force-pushed the fix/orig-inc-hook-refs branch from 768f46f to b98450c Compare May 8, 2026 05:51
@yuu-no
Copy link
Copy Markdown
Author

yuu-no commented May 8, 2026

Force-pushed after rebasing onto the new 1.0 history. The earlier commits targeted scripts/yath, which has been refactored into lib/App/Yath/Script/V1.pm on the current 1.0. The @ORIG_INC = @INC capture moved with it, and the same fix now lives at V1.pm:24. The Test2::Harness::Util::process_includes filter is unchanged from before. Description updated to reflect the new file path.

…ludes

Tools like Carmel inject blessed @inc hooks via PERL5OPT=-MCarmel::Setup.
yath snapshots @inc into two places that all assume plain strings:

  * App::Yath::Script::V1::call (settings->harness->orig_inc) — JSON
    encoded by write_settings_to and stringified into child -I flags.
  * Test2::Harness::Util::process_includes(include_current => 1) —
    runs through clean_path (realpath/rel2abs) which stringifies refs
    to bogus "HASH(0x...)" / "CODE(0x...)" / "ARRAY(0x...)" paths,
    then those leak into child -I flags.

Filter refs at both snapshot points. Hook refs cannot survive an
exec boundary as -I args anyway; the child reruns whatever PERL5OPT
injection the parent had.

Tests cover all three @inc hook forms documented in perlvar "@inc"
(blessed object, coderef, arrayref) end-to-end through `yath test`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yuu-no yuu-no force-pushed the fix/orig-inc-hook-refs branch from b98450c to 17790f0 Compare May 8, 2026 06:08
@yuu-no
Copy link
Copy Markdown
Author

yuu-no commented May 8, 2026

[MEMO]

> carmel exec -- yath --version

** Defaulting to the 'test' command **


Yath version: 1.000172

Extended Version Info
+----------------------------+----------+
| COMPONENT                  | VERSION  |
+----------------------------+----------+
| perl                       | v5.38.2  |
| App::Yath                  | 1.000172 |
| Test2::API                 | 1.302209 |
| Test2::Suite               | 1.302209 |
| Test::Builder              | 1.302209 |
| App::Yath::Plugin::Cover   | 1.000172 |
| App::Yath::Plugin::Git     | 1.000172 |
| App::Yath::Plugin::Notify  | 1.000172 |
| App::Yath::Plugin::SysInfo | 1.000172 |
| App::Yath::Plugin::YathUI  | 1.000172 |
+----------------------------+----------+
> carmel exec -- yath -Ilib -I. -r t

** Defaulting to the 'test' command **

encountered object 'Carmel::Runtime::FastINC=HASH(0x182d358)', but allow_blessed, allow_stringify or TO_JSON/FREEZE method missing at /home/{username}/.carmel/5.38.2-x86_64-linux/builds/Test2-Harness-1.000172/blib/lib/Test2/Harness/Util/JSON.pm line 58.

@yuu-no yuu-no marked this pull request as ready for review May 8, 2026 06:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant