Class: Henitai::Mutant::Activator
Overview
Activates a mutant inside the forked child process.
Defined Under Namespace
Modules: ConstantRedefinitionFilter
Class Method Summary
collapse
Instance Method Summary
collapse
#warn
Constructor Details
Returns a new instance of Activator.
770
|
# File 'sig/henitai.rbs', line 770
def initialize: () -> void
|
Class Method Details
.activate!(mutant) ⇒ Symbol?
26
27
28
|
# File 'lib/henitai/mutant/activator.rb', line 26
def self.activate!(mutant)
new.activate!(mutant)
end
|
.activation_source_for(mutant) ⇒ String?
Returns the define_method source string for mutant without
actually evaluating it. Used to pre-compute activation recipes.
Returns nil if the source cannot be computed (e.g. unsupported AST node).
33
34
35
36
37
|
# File 'lib/henitai/mutant/activator.rb', line 33
def self.activation_source_for(mutant)
new.send(:method_source, mutant)
rescue StandardError
nil
end
|
Instance Method Details
#activate!(mutant) ⇒ Symbol?
39
40
41
42
43
44
45
46
47
48
49
50
51
|
# File 'lib/henitai/mutant/activator.rb', line 39
def activate!(mutant)
subject = mutant.subject
raise ArgumentError, "Cannot activate wildcard subjects" if subject.method_name.nil?
source = mutant.precomputed_activation_source || method_source(mutant)
target = target_for(subject)
Henitai::WarningSilencer.silence do
target.class_eval(source, __FILE__, __LINE__ + 1)
nil
end
rescue Unparser::UnsupportedNodeError, SyntaxError
:compile_error
end
|
#body_source(mutant) ⇒ String
84
85
86
87
88
89
90
91
92
|
# File 'lib/henitai/mutant/activator.rb', line 84
def body_source(mutant)
subject_node = mutant.subject.ast_node
return compile_safe_unparse(mutant.mutated_node) unless subject_node
body = method_body(subject_node)
return compile_safe_unparse(Parser::AST::Node.new(:nil, [])) unless body
body_source_for_mutant(body, mutant)
end
|
#compile_safe_unparse(node) ⇒ String
211
212
213
214
215
|
# File 'lib/henitai/mutant/activator.rb', line 211
def compile_safe_unparse(node)
Unparser.unparse(node)
rescue StandardError => e
raise Unparser::UnsupportedNodeError, e.message
end
|
#load_source_file(subject) ⇒ void
This method returns an undefined value.
173
174
175
176
177
178
179
180
181
182
183
|
# File 'lib/henitai/mutant/activator.rb', line 173
def load_source_file(subject)
source_file = subject.source_file || source_file_from_ast(subject)
return unless source_file && File.file?(source_file)
Thread.current[:henitai_filter_const_warnings] = true
load(source_file)
loaded_feature = File.expand_path(source_file)
$LOADED_FEATURES << loaded_feature unless $LOADED_FEATURES.include?(loaded_feature)
ensure
Thread.current[:henitai_filter_const_warnings] = false
end
|
#load_target(subject) ⇒ Object
166
167
168
169
170
171
|
# File 'lib/henitai/mutant/activator.rb', line 166
def load_target(subject)
Object.const_get(subject.namespace.delete_prefix("::"))
rescue NameError
load_source_file(subject)
Object.const_get(subject.namespace.delete_prefix("::"))
end
|
#method_body(subject_node) ⇒ Object
113
114
115
116
117
118
119
120
121
122
123
124
|
# File 'lib/henitai/mutant/activator.rb', line 113
def method_body(subject_node)
case subject_node.type
when :def
subject_node.children[2]
when :defs
subject_node.children[3]
when :block
block_body(subject_node)
else
subject_node
end
end
|
#method_source(mutant) ⇒ String
The trailing module_function re-copy handles module_function subjects:
module_function copies the (private) instance method onto the module's
singleton, and define_method only replaces the instance side — without
the re-copy, callers of Module.method keep running the original code
and every mutant in such modules falsely survives. The guard checks
the pre-injection shape (private instance + singleton method on a
non-Class module) so a class-level method that merely shares an
instance method's name is never clobbered.
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
# File 'lib/henitai/mutant/activator.rb', line 68
def method_source(mutant)
method_name = mutant.subject.method_name
parameters = parameter_source(mutant)
replacement = body_source(mutant)
<<~RUBY
__henitai_module_function__ = !is_a?(Class) &&
private_method_defined?(:#{method_name}) &&
singleton_class.method_defined?(:#{method_name})
define_method(:#{method_name}) do |#{parameters}|
#{replacement}
end
module_function :#{method_name} if __henitai_module_function__
RUBY
end
|
#node_location_signature ⇒ Array[untyped]?
786
|
# File 'sig/henitai.rbs', line 786
def node_location_signature: (untyped) -> Array[untyped]?
|
#parameter_source(mutant) ⇒ String
126
127
128
|
# File 'lib/henitai/mutant/activator.rb', line 126
def parameter_source(mutant)
ParameterSource.new.build(mutant.subject.ast_node)
end
|
#replace_child ⇒ Object
783
|
# File 'sig/henitai.rbs', line 783
def replace_child: (untyped, untyped, untyped) -> untyped
|
#replace_node ⇒ Object
781
|
# File 'sig/henitai.rbs', line 781
def replace_node: (untyped, untyped, untyped) -> untyped
|
#same_node? ⇒ Boolean
782
|
# File 'sig/henitai.rbs', line 782
def same_node?: (untyped, untyped) -> bool
|
#source_file_from_ast(subject) ⇒ String?
185
186
187
188
189
190
191
192
193
194
195
196
|
# File 'lib/henitai/mutant/activator.rb', line 185
def source_file_from_ast(subject)
ast_node = subject.ast_node
return unless ast_node
location = ast_node.location
return unless location
expression = location.expression
return unless expression
expression.source_buffer.name
end
|
#target_for(subject) ⇒ Object
55
56
57
58
|
# File 'lib/henitai/mutant/activator.rb', line 55
def target_for(subject)
target = load_target(subject)
subject.method_type == :class ? target.singleton_class : target
end
|