Class: Evilution::Integration::Loading::BodyCallNeutralizer
- Inherits:
-
Object
- Object
- Evilution::Integration::Loading::BodyCallNeutralizer
- Defined in:
- lib/evilution/integration/loading/body_call_neutralizer.rb
Overview
Strip non-idempotent class/module-body side-effect calls from a mutated source before re-eval. Such calls (e.g. dry-monads ‘register_mixin`, plugin registries) raise on second invocation because they assume single-eval semantics. The first invocation already ran in the parent process during preload — the child fork inherits the resulting state, so re-running them is wasted work that aborts the eval before the mutated method takes effect.
Strategy: walk Prism tree, find CallNodes that sit directly under a class or module body (not inside a def). Calls on a small allowlist of patterns known to be idempotent (‘include`, `attr_*`, visibility modifiers, etc.) are preserved; everything else is replaced byte-for-byte with `nil`.
Constant Summary collapse
- IDEMPOTENT_CALLS =
%i[ include extend prepend using attr_reader attr_writer attr_accessor private public protected module_function private_class_method public_class_method alias_method define_method define_singleton_method delegate require require_relative autoload ].to_set.freeze
Class Attribute Summary collapse
-
.preloaded_features ⇒ Object
Snapshot of ‘$LOADED_FEATURES` captured at parent preload-end (or lazily initialised on first access).
Class Method Summary collapse
Instance Method Summary collapse
-
#call(source, file_path: nil) ⇒ Object
‘file_path` (optional) lets callers gate neutralization on whether the target file was actually preloaded into the parent.
Class Attribute Details
.preloaded_features ⇒ Object
Snapshot of ‘$LOADED_FEATURES` captured at parent preload-end (or lazily initialised on first access). Forks inherit this set via copy-on-write, so worker processes see the same membership the parent saw when it finished its preload phase. Frozen so in-place mutation cannot silently change neutralization semantics or force child forks to copy the page.
36 37 38 |
# File 'lib/evilution/integration/loading/body_call_neutralizer.rb', line 36 def preloaded_features @preloaded_features ||= $LOADED_FEATURES.to_set.freeze end |
Class Method Details
.reset_preload_snapshot! ⇒ Object
40 41 42 |
# File 'lib/evilution/integration/loading/body_call_neutralizer.rb', line 40 def reset_preload_snapshot! @preloaded_features = nil end |
Instance Method Details
#call(source, file_path: nil) ⇒ Object
‘file_path` (optional) lets callers gate neutralization on whether the target file was actually preloaded into the parent. The neutralizer’s premise — “this body has already run once, re-running it would double- register” — only holds when the parent loaded the file. Lazy-loaded plugin files (e.g. roda’s ‘lib/roda/plugins/typecast_params.rb`, which the gem only requires when the user opts in via `plugin :typecast_params`) are first-loaded inside the child fork, so neutralizing their DSL calls strips method definitions that subsequent sibling statements (alias, etc.) depend on, producing cascading NameError. Callers that don’t pass a path get the legacy always-neutralize behavior.
55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/evilution/integration/loading/body_call_neutralizer.rb', line 55 def call(source, file_path: nil) return source if file_path && !preloaded?(file_path) result = Prism.parse(source) return source if result.failure? edits = collect_edits(result.value) return source if edits.empty? apply_edits(source, edits) end |