syntax_tree 5.1.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,628 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module SyntaxTree
6
+ # This module provides an object representation of the YARV bytecode.
7
+ module YARV
8
+ class VM
9
+ class Jump
10
+ attr_reader :label
11
+
12
+ def initialize(label)
13
+ @label = label
14
+ end
15
+ end
16
+
17
+ class Leave
18
+ attr_reader :value
19
+
20
+ def initialize(value)
21
+ @value = value
22
+ end
23
+ end
24
+
25
+ class Frame
26
+ attr_reader :iseq, :parent, :stack_index, :_self, :nesting, :svars
27
+ attr_accessor :line, :pc
28
+
29
+ def initialize(iseq, parent, stack_index, _self, nesting)
30
+ @iseq = iseq
31
+ @parent = parent
32
+ @stack_index = stack_index
33
+ @_self = _self
34
+ @nesting = nesting
35
+
36
+ @svars = {}
37
+ @line = iseq.line
38
+ @pc = 0
39
+ end
40
+ end
41
+
42
+ class TopFrame < Frame
43
+ def initialize(iseq)
44
+ super(iseq, nil, 0, TOPLEVEL_BINDING.eval("self"), [Object])
45
+ end
46
+ end
47
+
48
+ class BlockFrame < Frame
49
+ def initialize(iseq, parent, stack_index)
50
+ super(iseq, parent, stack_index, parent._self, parent.nesting)
51
+ end
52
+ end
53
+
54
+ class MethodFrame < Frame
55
+ attr_reader :name, :block
56
+
57
+ def initialize(iseq, nesting, parent, stack_index, _self, name, block)
58
+ super(iseq, parent, stack_index, _self, nesting)
59
+ @name = name
60
+ @block = block
61
+ end
62
+ end
63
+
64
+ class ClassFrame < Frame
65
+ def initialize(iseq, parent, stack_index, _self)
66
+ super(iseq, parent, stack_index, _self, parent.nesting + [_self])
67
+ end
68
+ end
69
+
70
+ class RescueFrame < Frame
71
+ def initialize(iseq, parent, stack_index)
72
+ super(iseq, parent, stack_index, parent._self, parent.nesting)
73
+ end
74
+ end
75
+
76
+ class ThrownError < StandardError
77
+ attr_reader :value
78
+
79
+ def initialize(value, backtrace)
80
+ super("This error was thrown by the Ruby VM.")
81
+ @value = value
82
+ set_backtrace(backtrace)
83
+ end
84
+ end
85
+
86
+ class ReturnError < ThrownError
87
+ end
88
+
89
+ class BreakError < ThrownError
90
+ end
91
+
92
+ class NextError < ThrownError
93
+ end
94
+
95
+ class FrozenCore
96
+ define_method("core#hash_merge_kwd") { |left, right| left.merge(right) }
97
+
98
+ define_method("core#hash_merge_ptr") do |hash, *values|
99
+ hash.merge(values.each_slice(2).to_h)
100
+ end
101
+
102
+ define_method("core#set_method_alias") do |clazz, new_name, old_name|
103
+ clazz.alias_method(new_name, old_name)
104
+ end
105
+
106
+ define_method("core#set_variable_alias") do |new_name, old_name|
107
+ # Using eval here since there isn't a reflection API to be able to
108
+ # alias global variables.
109
+ eval("alias #{new_name} #{old_name}", binding, __FILE__, __LINE__)
110
+ end
111
+
112
+ define_method("core#set_postexe") { |&block| END { block.call } }
113
+
114
+ define_method("core#undef_method") do |clazz, name|
115
+ clazz.undef_method(name)
116
+ nil
117
+ end
118
+ end
119
+
120
+ # This is the main entrypoint for events firing in the VM, which allows
121
+ # us to implement tracing.
122
+ class NullEvents
123
+ def publish_frame_change(frame)
124
+ end
125
+
126
+ def publish_instruction(iseq, insn)
127
+ end
128
+
129
+ def publish_stack_change(stack)
130
+ end
131
+
132
+ def publish_tracepoint(event)
133
+ end
134
+ end
135
+
136
+ # This is a simple implementation of tracing that prints to STDOUT.
137
+ class STDOUTEvents
138
+ attr_reader :disassembler
139
+
140
+ def initialize
141
+ @disassembler = Disassembler.new
142
+ end
143
+
144
+ def publish_frame_change(frame)
145
+ puts "%-16s %s" % ["frame-change", "#{frame.iseq.file}@#{frame.line}"]
146
+ end
147
+
148
+ def publish_instruction(iseq, insn)
149
+ disassembler.current_iseq = iseq
150
+ puts "%-16s %s" % ["instruction", insn.disasm(disassembler)]
151
+ end
152
+
153
+ def publish_stack_change(stack)
154
+ puts "%-16s %s" % ["stack-change", stack.values.inspect]
155
+ end
156
+
157
+ def publish_tracepoint(event)
158
+ puts "%-16s %s" % ["tracepoint", event.inspect]
159
+ end
160
+ end
161
+
162
+ # This represents the global VM stack. It effectively is an array, but
163
+ # wraps mutating functions with instrumentation.
164
+ class Stack
165
+ attr_reader :events, :values
166
+
167
+ def initialize(events)
168
+ @events = events
169
+ @values = []
170
+ end
171
+
172
+ def concat(...)
173
+ values.concat(...).tap { events.publish_stack_change(self) }
174
+ end
175
+
176
+ def last
177
+ values.last
178
+ end
179
+
180
+ def length
181
+ values.length
182
+ end
183
+
184
+ def push(...)
185
+ values.push(...).tap { events.publish_stack_change(self) }
186
+ end
187
+
188
+ def pop(...)
189
+ values.pop(...).tap { events.publish_stack_change(self) }
190
+ end
191
+
192
+ def slice!(...)
193
+ values.slice!(...).tap { events.publish_stack_change(self) }
194
+ end
195
+
196
+ def [](...)
197
+ values.[](...)
198
+ end
199
+
200
+ def []=(...)
201
+ values.[]=(...).tap { events.publish_stack_change(self) }
202
+ end
203
+ end
204
+
205
+ FROZEN_CORE = FrozenCore.new.freeze
206
+
207
+ extend Forwardable
208
+
209
+ attr_reader :events
210
+
211
+ attr_reader :stack
212
+ def_delegators :stack, :push, :pop
213
+
214
+ attr_reader :frame
215
+
216
+ def initialize(events = NullEvents.new)
217
+ @events = events
218
+ @stack = Stack.new(events)
219
+ @frame = nil
220
+ end
221
+
222
+ def self.run(iseq)
223
+ new.run_top_frame(iseq)
224
+ end
225
+
226
+ ##########################################################################
227
+ # Helper methods for frames
228
+ ##########################################################################
229
+
230
+ def run_frame(frame)
231
+ # First, set the current frame to the given value.
232
+ previous = @frame
233
+ @frame = frame
234
+ events.publish_frame_change(@frame)
235
+
236
+ # Next, set up the local table for the frame. This is actually incorrect
237
+ # as it could use the values already on the stack, but for now we're
238
+ # just doing this for simplicity.
239
+ stack.concat(Array.new(frame.iseq.local_table.size))
240
+
241
+ # Yield so that some frame-specific setup can be done.
242
+ start_label = yield if block_given?
243
+ frame.pc = frame.iseq.insns.index(start_label) if start_label
244
+
245
+ # Finally we can execute the instructions one at a time. If they return
246
+ # jumps or leaves we will handle those appropriately.
247
+ loop do
248
+ case (insn = frame.iseq.insns[frame.pc])
249
+ when Integer
250
+ frame.line = insn
251
+ frame.pc += 1
252
+ when Symbol
253
+ events.publish_tracepoint(insn)
254
+ frame.pc += 1
255
+ when InstructionSequence::Label
256
+ # skip labels
257
+ frame.pc += 1
258
+ else
259
+ begin
260
+ events.publish_instruction(frame.iseq, insn)
261
+ result = insn.call(self)
262
+ rescue ReturnError => error
263
+ raise if frame.iseq.type != :method
264
+
265
+ stack.slice!(frame.stack_index..)
266
+ @frame = frame.parent
267
+ events.publish_frame_change(@frame)
268
+
269
+ return error.value
270
+ rescue BreakError => error
271
+ raise if frame.iseq.type != :block
272
+
273
+ catch_entry =
274
+ find_catch_entry(frame, InstructionSequence::CatchBreak)
275
+ raise unless catch_entry
276
+
277
+ stack.slice!(
278
+ (
279
+ frame.stack_index + frame.iseq.local_table.size +
280
+ catch_entry.restore_sp
281
+ )..
282
+ )
283
+ @frame = frame
284
+ events.publish_frame_change(@frame)
285
+
286
+ frame.pc = frame.iseq.insns.index(catch_entry.exit_label)
287
+ push(result = error.value)
288
+ rescue NextError => error
289
+ raise if frame.iseq.type != :block
290
+
291
+ catch_entry =
292
+ find_catch_entry(frame, InstructionSequence::CatchNext)
293
+ raise unless catch_entry
294
+
295
+ stack.slice!(
296
+ (
297
+ frame.stack_index + frame.iseq.local_table.size +
298
+ catch_entry.restore_sp
299
+ )..
300
+ )
301
+ @frame = frame
302
+ events.publish_frame_change(@frame)
303
+
304
+ frame.pc = frame.iseq.insns.index(catch_entry.exit_label)
305
+ push(result = error.value)
306
+ rescue Exception => error
307
+ catch_entry =
308
+ find_catch_entry(frame, InstructionSequence::CatchRescue)
309
+ raise unless catch_entry
310
+
311
+ stack.slice!(
312
+ (
313
+ frame.stack_index + frame.iseq.local_table.size +
314
+ catch_entry.restore_sp
315
+ )..
316
+ )
317
+ @frame = frame
318
+ events.publish_frame_change(@frame)
319
+
320
+ frame.pc = frame.iseq.insns.index(catch_entry.exit_label)
321
+ push(result = run_rescue_frame(catch_entry.iseq, frame, error))
322
+ end
323
+
324
+ case result
325
+ when Jump
326
+ frame.pc = frame.iseq.insns.index(result.label) + 1
327
+ when Leave
328
+ # this shouldn't be necessary, but is because we're not handling
329
+ # the stack correctly at the moment
330
+ stack.slice!(frame.stack_index..)
331
+
332
+ # restore the previous frame
333
+ @frame = previous || frame.parent
334
+ events.publish_frame_change(@frame) if @frame
335
+
336
+ return result.value
337
+ else
338
+ frame.pc += 1
339
+ end
340
+ end
341
+ end
342
+ end
343
+
344
+ def find_catch_entry(frame, type)
345
+ iseq = frame.iseq
346
+ iseq.catch_table.find do |catch_entry|
347
+ next unless catch_entry.is_a?(type)
348
+
349
+ begin_pc = iseq.insns.index(catch_entry.begin_label)
350
+ end_pc = iseq.insns.index(catch_entry.end_label)
351
+
352
+ (begin_pc...end_pc).cover?(frame.pc)
353
+ end
354
+ end
355
+
356
+ def run_top_frame(iseq)
357
+ run_frame(TopFrame.new(iseq))
358
+ end
359
+
360
+ def run_block_frame(iseq, frame, *args, **kwargs, &block)
361
+ run_frame(BlockFrame.new(iseq, frame, stack.length)) do
362
+ setup_arguments(iseq, args, kwargs, block)
363
+ end
364
+ end
365
+
366
+ def run_class_frame(iseq, clazz)
367
+ run_frame(ClassFrame.new(iseq, frame, stack.length, clazz))
368
+ end
369
+
370
+ def run_method_frame(name, nesting, iseq, _self, *args, **kwargs, &block)
371
+ run_frame(
372
+ MethodFrame.new(
373
+ iseq,
374
+ nesting,
375
+ frame,
376
+ stack.length,
377
+ _self,
378
+ name,
379
+ block
380
+ )
381
+ ) { setup_arguments(iseq, args, kwargs, block) }
382
+ end
383
+
384
+ def run_rescue_frame(iseq, frame, error)
385
+ run_frame(RescueFrame.new(iseq, frame, stack.length)) do
386
+ local_set(0, 0, error)
387
+ nil
388
+ end
389
+ end
390
+
391
+ def setup_arguments(iseq, args, kwargs, block)
392
+ locals = [*args]
393
+ local_index = 0
394
+ start_label = nil
395
+
396
+ # First, set up all of the leading arguments. These are positional and
397
+ # required arguments at the start of the argument list.
398
+ if (lead_num = iseq.argument_options[:lead_num])
399
+ lead_num.times do
400
+ local_set(local_index, 0, locals.shift)
401
+ local_index += 1
402
+ end
403
+ end
404
+
405
+ # Next, set up all of the optional arguments. The opt array contains
406
+ # the labels that the frame should start at if the optional is
407
+ # present. The last element of the array is the label that the frame
408
+ # should start at if all of the optional arguments are present.
409
+ if (opt = iseq.argument_options[:opt])
410
+ opt[0...-1].each do |label|
411
+ if locals.empty?
412
+ start_label = label
413
+ break
414
+ else
415
+ local_set(local_index, 0, locals.shift)
416
+ local_index += 1
417
+ end
418
+
419
+ start_label = opt.last if start_label.nil?
420
+ end
421
+ end
422
+
423
+ # If there is a splat argument, then we'll set that up here. It will
424
+ # grab up all of the remaining positional arguments.
425
+ if (rest_start = iseq.argument_options[:rest_start])
426
+ if (post_start = iseq.argument_options[:post_start])
427
+ length = post_start - rest_start
428
+ local_set(local_index, 0, locals[0...length])
429
+ locals = locals[length..]
430
+ else
431
+ local_set(local_index, 0, locals.dup)
432
+ locals.clear
433
+ end
434
+ local_index += 1
435
+ end
436
+
437
+ # Next, set up any post arguments. These are positional arguments that
438
+ # come after the splat argument.
439
+ if (post_num = iseq.argument_options[:post_num])
440
+ post_num.times do
441
+ local_set(local_index, 0, locals.shift)
442
+ local_index += 1
443
+ end
444
+ end
445
+
446
+ if (keyword_option = iseq.argument_options[:keyword])
447
+ # First, set up the keyword bits array.
448
+ keyword_bits =
449
+ keyword_option.map do |config|
450
+ kwargs.key?(config.is_a?(Array) ? config[0] : config)
451
+ end
452
+
453
+ iseq.local_table.locals.each_with_index do |local, index|
454
+ # If this is the keyword bits local, then set it appropriately.
455
+ if local.name.is_a?(Integer)
456
+ local_set(index, 0, keyword_bits)
457
+ next
458
+ end
459
+
460
+ # First, find the configuration for this local in the keywords
461
+ # list if it exists.
462
+ name = local.name
463
+ config =
464
+ keyword_option.find do |keyword|
465
+ keyword.is_a?(Array) ? keyword[0] == name : keyword == name
466
+ end
467
+
468
+ # If the configuration doesn't exist, then the local is not a
469
+ # keyword local.
470
+ next unless config
471
+
472
+ if !config.is_a?(Array)
473
+ # required keyword
474
+ local_set(index, 0, kwargs.fetch(name))
475
+ elsif !config[1].nil?
476
+ # optional keyword with embedded default value
477
+ local_set(index, 0, kwargs.fetch(name, config[1]))
478
+ else
479
+ # optional keyword with expression default value
480
+ local_set(index, 0, kwargs[name])
481
+ end
482
+ end
483
+ end
484
+
485
+ local_set(local_index, 0, block) if iseq.argument_options[:block_start]
486
+
487
+ start_label
488
+ end
489
+
490
+ ##########################################################################
491
+ # Helper methods for instructions
492
+ ##########################################################################
493
+
494
+ def const_base
495
+ frame.nesting.last
496
+ end
497
+
498
+ def frame_at(level)
499
+ current = frame
500
+ level.times { current = current.parent }
501
+ current
502
+ end
503
+
504
+ def frame_svar
505
+ current = frame
506
+ current = current.parent while current.is_a?(BlockFrame)
507
+ current
508
+ end
509
+
510
+ def frame_yield
511
+ current = frame
512
+ current = current.parent until current.is_a?(MethodFrame)
513
+ current
514
+ end
515
+
516
+ def frozen_core
517
+ FROZEN_CORE
518
+ end
519
+
520
+ def jump(label)
521
+ Jump.new(label)
522
+ end
523
+
524
+ def leave
525
+ Leave.new(pop)
526
+ end
527
+
528
+ def local_get(index, level)
529
+ stack[frame_at(level).stack_index + index]
530
+ end
531
+
532
+ def local_set(index, level, value)
533
+ stack[frame_at(level).stack_index + index] = value
534
+ end
535
+
536
+ ##########################################################################
537
+ # Methods for overriding runtime behavior
538
+ ##########################################################################
539
+
540
+ DLEXT = ".#{RbConfig::CONFIG["DLEXT"]}"
541
+ SOEXT = ".#{RbConfig::CONFIG["SOEXT"]}"
542
+
543
+ def require_resolved(filepath)
544
+ $LOADED_FEATURES << filepath
545
+ iseq = RubyVM::InstructionSequence.compile_file(filepath)
546
+ run_top_frame(InstructionSequence.from(iseq.to_a))
547
+ end
548
+
549
+ def require_internal(filepath, loading: false)
550
+ case (extname = File.extname(filepath))
551
+ when ""
552
+ # search for all the extensions
553
+ searching = filepath
554
+ extensions = ["", ".rb", DLEXT, SOEXT]
555
+ when ".rb", DLEXT, SOEXT
556
+ # search only for the given extension name
557
+ searching = File.basename(filepath, extname)
558
+ extensions = [extname]
559
+ else
560
+ # we don't handle these extensions, raise a load error
561
+ raise LoadError, "cannot load such file -- #{filepath}"
562
+ end
563
+
564
+ if filepath.start_with?("/")
565
+ # absolute path, search only in the given directory
566
+ directories = [File.dirname(searching)]
567
+ searching = File.basename(searching)
568
+ else
569
+ # relative path, search in the load path
570
+ directories = $LOAD_PATH
571
+ end
572
+
573
+ directories.each do |directory|
574
+ extensions.each do |extension|
575
+ absolute_path = File.join(directory, "#{searching}#{extension}")
576
+ next unless File.exist?(absolute_path)
577
+
578
+ if !loading && $LOADED_FEATURES.include?(absolute_path)
579
+ return false
580
+ elsif extension == ".rb"
581
+ require_resolved(absolute_path)
582
+ return true
583
+ elsif loading
584
+ return Kernel.send(:yarv_load, filepath)
585
+ else
586
+ return Kernel.send(:yarv_require, filepath)
587
+ end
588
+ end
589
+ end
590
+
591
+ if loading
592
+ Kernel.send(:yarv_load, filepath)
593
+ else
594
+ Kernel.send(:yarv_require, filepath)
595
+ end
596
+ end
597
+
598
+ def require(filepath)
599
+ require_internal(filepath, loading: false)
600
+ end
601
+
602
+ def require_relative(filepath)
603
+ Kernel.yarv_require_relative(filepath)
604
+ end
605
+
606
+ def load(filepath)
607
+ require_internal(filepath, loading: true)
608
+ end
609
+
610
+ def eval(
611
+ source,
612
+ binding = TOPLEVEL_BINDING,
613
+ filename = "(eval)",
614
+ lineno = 1
615
+ )
616
+ Kernel.yarv_eval(source, binding, filename, lineno)
617
+ end
618
+
619
+ def throw(tag, value = nil)
620
+ Kernel.throw(tag, value)
621
+ end
622
+
623
+ def catch(tag, &block)
624
+ Kernel.catch(tag, &block)
625
+ end
626
+ end
627
+ end
628
+ end