syntax_tree 5.1.0 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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