diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index 5831c4256..d73ae9cb5 100644 --- a/src/main/java/org/perlonjava/core/Configuration.java +++ b/src/main/java/org/perlonjava/core/Configuration.java @@ -33,14 +33,14 @@ public final class Configuration { * Automatically populated by Gradle/Maven during build. * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String gitCommitId = "d7eacf972"; + public static final String gitCommitId = "82e5e452d"; /** * Git commit date of the build (ISO format: YYYY-MM-DD). * Automatically populated by Gradle/Maven during build. * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String gitCommitDate = "2026-04-28"; + public static final String gitCommitDate = "2026-04-29"; /** * Build timestamp in Perl 5 "Compiled at" format (e.g., "Apr 7 2026 11:20:00"). @@ -48,7 +48,7 @@ public final class Configuration { * Parsed by App::perlbrew and other tools via: perl -V | grep "Compiled at" * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String buildTimestamp = "Apr 28 2026 21:49:56"; + public static final String buildTimestamp = "Apr 29 2026 09:31:10"; // Prevent instantiation private Configuration() { diff --git a/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java b/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java index 4a345f932..edc2dec08 100644 --- a/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java +++ b/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java @@ -711,7 +711,14 @@ else if (code == null) { // Notify B::Hooks::EndOfScope that we're starting to load a file // This enables on_scope_end callbacks to know which file they belong to BHooksEndOfScope.beginFileLoad(parsedArgs.fileName); - + + // Source filters installed in the caller's file must not leak into + // the file being required/do'd. Save the outer filter state and start + // with a clean state for this compilation unit; restore on the way out. + org.perlonjava.runtime.perlmodule.FilterUtilCall.FilterStateSnapshot + filterSnapshot = org.perlonjava.runtime.perlmodule.FilterUtilCall + .saveAndResetFilterState(); + try { featureManager = new FeatureFlags(); @@ -755,6 +762,11 @@ else if (code == null) { // Restore the caller's hints hash hintHash.elements.clear(); hintHash.elements.putAll(savedHintHash); + + // Restore the caller's source-filter state (filters installed in + // the required file must not leak back to the caller). + org.perlonjava.runtime.perlmodule.FilterUtilCall + .restoreFilterState(filterSnapshot); } // Return result based on context diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/FilterUtilCall.java b/src/main/java/org/perlonjava/runtime/perlmodule/FilterUtilCall.java index a21fc7b44..440224c96 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/FilterUtilCall.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/FilterUtilCall.java @@ -468,6 +468,52 @@ public static void clearFilters() { context.currentLine = 0; } + /** + * Snapshot of filter state (stack + "installed during use" flag). + * Source filters are scoped to the file/compilation unit in which + * they were installed; when we begin parsing a new file (e.g. via + * {@code require}/{@code do}), we save the outer state, start with + * a clean state, and restore on the way out. + */ + public static class FilterStateSnapshot { + final RuntimeList filterStack; + final boolean installedDuringUse; + + FilterStateSnapshot(RuntimeList filterStack, boolean installedDuringUse) { + this.filterStack = filterStack; + this.installedDuringUse = installedDuringUse; + } + } + + /** + * Save the current filter state and reset to a clean state. + * Call this before compiling a new file (require/do); pair with + * {@link #restoreFilterState(FilterStateSnapshot)}. + */ + public static FilterStateSnapshot saveAndResetFilterState() { + FilterContext context = filterContext.get(); + FilterStateSnapshot snapshot = + new FilterStateSnapshot(context.filterStack, filterInstalledDuringUse.get()); + context.filterStack = new RuntimeList(); + context.sourceLines = null; + context.currentLine = 0; + filterInstalledDuringUse.set(false); + return snapshot; + } + + /** + * Restore filter state previously saved by + * {@link #saveAndResetFilterState()}. + */ + public static void restoreFilterState(FilterStateSnapshot snapshot) { + if (snapshot == null) return; + FilterContext context = filterContext.get(); + context.filterStack = snapshot.filterStack; + context.sourceLines = null; + context.currentLine = 0; + filterInstalledDuringUse.set(snapshot.installedDuringUse); + } + /** * Context for managing active source filters. */