Class: Evilution::Integration::Loading::BodyCallNeutralizer Private
- Inherits:
-
Object
- Object
- Evilution::Integration::Loading::BodyCallNeutralizer
- Defined in:
- lib/evilution/integration/loading/body_call_neutralizer.rb
Overview
This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.
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 =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
%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
private
Snapshot of
$LOADED_FEATUREScaptured at parent preload-end (or lazily initialised on first access).
Class Method Summary collapse
Instance Method Summary collapse
-
#call(source, file_path: nil) ⇒ Object
private
file_path(optional) lets callers gate neutralization on whether the target file was actually preloaded into the parent.
Class Attribute Details
.preloaded_features ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
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
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
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
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
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 |