syntax_tree 5.1.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,624 @@
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
+ ##########################################################################
223
+ # Helper methods for frames
224
+ ##########################################################################
225
+
226
+ def run_frame(frame)
227
+ # First, set the current frame to the given value.
228
+ previous = @frame
229
+ @frame = frame
230
+ events.publish_frame_change(@frame)
231
+
232
+ # Next, set up the local table for the frame. This is actually incorrect
233
+ # as it could use the values already on the stack, but for now we're
234
+ # just doing this for simplicity.
235
+ stack.concat(Array.new(frame.iseq.local_table.size))
236
+
237
+ # Yield so that some frame-specific setup can be done.
238
+ start_label = yield if block_given?
239
+ frame.pc = frame.iseq.insns.index(start_label) if start_label
240
+
241
+ # Finally we can execute the instructions one at a time. If they return
242
+ # jumps or leaves we will handle those appropriately.
243
+ loop do
244
+ case (insn = frame.iseq.insns[frame.pc])
245
+ when Integer
246
+ frame.line = insn
247
+ frame.pc += 1
248
+ when Symbol
249
+ events.publish_tracepoint(insn)
250
+ frame.pc += 1
251
+ when InstructionSequence::Label
252
+ # skip labels
253
+ frame.pc += 1
254
+ else
255
+ begin
256
+ events.publish_instruction(frame.iseq, insn)
257
+ result = insn.call(self)
258
+ rescue ReturnError => error
259
+ raise if frame.iseq.type != :method
260
+
261
+ stack.slice!(frame.stack_index..)
262
+ @frame = frame.parent
263
+ events.publish_frame_change(@frame)
264
+
265
+ return error.value
266
+ rescue BreakError => error
267
+ raise if frame.iseq.type != :block
268
+
269
+ catch_entry =
270
+ find_catch_entry(frame, InstructionSequence::CatchBreak)
271
+ raise unless catch_entry
272
+
273
+ stack.slice!(
274
+ (
275
+ frame.stack_index + frame.iseq.local_table.size +
276
+ catch_entry.restore_sp
277
+ )..
278
+ )
279
+ @frame = frame
280
+ events.publish_frame_change(@frame)
281
+
282
+ frame.pc = frame.iseq.insns.index(catch_entry.exit_label)
283
+ push(result = error.value)
284
+ rescue NextError => error
285
+ raise if frame.iseq.type != :block
286
+
287
+ catch_entry =
288
+ find_catch_entry(frame, InstructionSequence::CatchNext)
289
+ raise unless catch_entry
290
+
291
+ stack.slice!(
292
+ (
293
+ frame.stack_index + frame.iseq.local_table.size +
294
+ catch_entry.restore_sp
295
+ )..
296
+ )
297
+ @frame = frame
298
+ events.publish_frame_change(@frame)
299
+
300
+ frame.pc = frame.iseq.insns.index(catch_entry.exit_label)
301
+ push(result = error.value)
302
+ rescue Exception => error
303
+ catch_entry =
304
+ find_catch_entry(frame, InstructionSequence::CatchRescue)
305
+ raise unless catch_entry
306
+
307
+ stack.slice!(
308
+ (
309
+ frame.stack_index + frame.iseq.local_table.size +
310
+ catch_entry.restore_sp
311
+ )..
312
+ )
313
+ @frame = frame
314
+ events.publish_frame_change(@frame)
315
+
316
+ frame.pc = frame.iseq.insns.index(catch_entry.exit_label)
317
+ push(result = run_rescue_frame(catch_entry.iseq, frame, error))
318
+ end
319
+
320
+ case result
321
+ when Jump
322
+ frame.pc = frame.iseq.insns.index(result.label) + 1
323
+ when Leave
324
+ # this shouldn't be necessary, but is because we're not handling
325
+ # the stack correctly at the moment
326
+ stack.slice!(frame.stack_index..)
327
+
328
+ # restore the previous frame
329
+ @frame = previous || frame.parent
330
+ events.publish_frame_change(@frame) if @frame
331
+
332
+ return result.value
333
+ else
334
+ frame.pc += 1
335
+ end
336
+ end
337
+ end
338
+ end
339
+
340
+ def find_catch_entry(frame, type)
341
+ iseq = frame.iseq
342
+ iseq.catch_table.find do |catch_entry|
343
+ next unless catch_entry.is_a?(type)
344
+
345
+ begin_pc = iseq.insns.index(catch_entry.begin_label)
346
+ end_pc = iseq.insns.index(catch_entry.end_label)
347
+
348
+ (begin_pc...end_pc).cover?(frame.pc)
349
+ end
350
+ end
351
+
352
+ def run_top_frame(iseq)
353
+ run_frame(TopFrame.new(iseq))
354
+ end
355
+
356
+ def run_block_frame(iseq, frame, *args, **kwargs, &block)
357
+ run_frame(BlockFrame.new(iseq, frame, stack.length)) do
358
+ setup_arguments(iseq, args, kwargs, block)
359
+ end
360
+ end
361
+
362
+ def run_class_frame(iseq, clazz)
363
+ run_frame(ClassFrame.new(iseq, frame, stack.length, clazz))
364
+ end
365
+
366
+ def run_method_frame(name, nesting, iseq, _self, *args, **kwargs, &block)
367
+ run_frame(
368
+ MethodFrame.new(
369
+ iseq,
370
+ nesting,
371
+ frame,
372
+ stack.length,
373
+ _self,
374
+ name,
375
+ block
376
+ )
377
+ ) { setup_arguments(iseq, args, kwargs, block) }
378
+ end
379
+
380
+ def run_rescue_frame(iseq, frame, error)
381
+ run_frame(RescueFrame.new(iseq, frame, stack.length)) do
382
+ local_set(0, 0, error)
383
+ nil
384
+ end
385
+ end
386
+
387
+ def setup_arguments(iseq, args, kwargs, block)
388
+ locals = [*args]
389
+ local_index = 0
390
+ start_label = nil
391
+
392
+ # First, set up all of the leading arguments. These are positional and
393
+ # required arguments at the start of the argument list.
394
+ if (lead_num = iseq.argument_options[:lead_num])
395
+ lead_num.times do
396
+ local_set(local_index, 0, locals.shift)
397
+ local_index += 1
398
+ end
399
+ end
400
+
401
+ # Next, set up all of the optional arguments. The opt array contains
402
+ # the labels that the frame should start at if the optional is
403
+ # present. The last element of the array is the label that the frame
404
+ # should start at if all of the optional arguments are present.
405
+ if (opt = iseq.argument_options[:opt])
406
+ opt[0...-1].each do |label|
407
+ if locals.empty?
408
+ start_label = label
409
+ break
410
+ else
411
+ local_set(local_index, 0, locals.shift)
412
+ local_index += 1
413
+ end
414
+
415
+ start_label = opt.last if start_label.nil?
416
+ end
417
+ end
418
+
419
+ # If there is a splat argument, then we'll set that up here. It will
420
+ # grab up all of the remaining positional arguments.
421
+ if (rest_start = iseq.argument_options[:rest_start])
422
+ if (post_start = iseq.argument_options[:post_start])
423
+ length = post_start - rest_start
424
+ local_set(local_index, 0, locals[0...length])
425
+ locals = locals[length..]
426
+ else
427
+ local_set(local_index, 0, locals.dup)
428
+ locals.clear
429
+ end
430
+ local_index += 1
431
+ end
432
+
433
+ # Next, set up any post arguments. These are positional arguments that
434
+ # come after the splat argument.
435
+ if (post_num = iseq.argument_options[:post_num])
436
+ post_num.times do
437
+ local_set(local_index, 0, locals.shift)
438
+ local_index += 1
439
+ end
440
+ end
441
+
442
+ if (keyword_option = iseq.argument_options[:keyword])
443
+ # First, set up the keyword bits array.
444
+ keyword_bits =
445
+ keyword_option.map do |config|
446
+ kwargs.key?(config.is_a?(Array) ? config[0] : config)
447
+ end
448
+
449
+ iseq.local_table.locals.each_with_index do |local, index|
450
+ # If this is the keyword bits local, then set it appropriately.
451
+ if local.name.is_a?(Integer)
452
+ local_set(index, 0, keyword_bits)
453
+ next
454
+ end
455
+
456
+ # First, find the configuration for this local in the keywords
457
+ # list if it exists.
458
+ name = local.name
459
+ config =
460
+ keyword_option.find do |keyword|
461
+ keyword.is_a?(Array) ? keyword[0] == name : keyword == name
462
+ end
463
+
464
+ # If the configuration doesn't exist, then the local is not a
465
+ # keyword local.
466
+ next unless config
467
+
468
+ if !config.is_a?(Array)
469
+ # required keyword
470
+ local_set(index, 0, kwargs.fetch(name))
471
+ elsif !config[1].nil?
472
+ # optional keyword with embedded default value
473
+ local_set(index, 0, kwargs.fetch(name, config[1]))
474
+ else
475
+ # optional keyword with expression default value
476
+ local_set(index, 0, kwargs[name])
477
+ end
478
+ end
479
+ end
480
+
481
+ local_set(local_index, 0, block) if iseq.argument_options[:block_start]
482
+
483
+ start_label
484
+ end
485
+
486
+ ##########################################################################
487
+ # Helper methods for instructions
488
+ ##########################################################################
489
+
490
+ def const_base
491
+ frame.nesting.last
492
+ end
493
+
494
+ def frame_at(level)
495
+ current = frame
496
+ level.times { current = current.parent }
497
+ current
498
+ end
499
+
500
+ def frame_svar
501
+ current = frame
502
+ current = current.parent while current.is_a?(BlockFrame)
503
+ current
504
+ end
505
+
506
+ def frame_yield
507
+ current = frame
508
+ current = current.parent until current.is_a?(MethodFrame)
509
+ current
510
+ end
511
+
512
+ def frozen_core
513
+ FROZEN_CORE
514
+ end
515
+
516
+ def jump(label)
517
+ Jump.new(label)
518
+ end
519
+
520
+ def leave
521
+ Leave.new(pop)
522
+ end
523
+
524
+ def local_get(index, level)
525
+ stack[frame_at(level).stack_index + index]
526
+ end
527
+
528
+ def local_set(index, level, value)
529
+ stack[frame_at(level).stack_index + index] = value
530
+ end
531
+
532
+ ##########################################################################
533
+ # Methods for overriding runtime behavior
534
+ ##########################################################################
535
+
536
+ DLEXT = ".#{RbConfig::CONFIG["DLEXT"]}"
537
+ SOEXT = ".#{RbConfig::CONFIG["SOEXT"]}"
538
+
539
+ def require_resolved(filepath)
540
+ $LOADED_FEATURES << filepath
541
+ iseq = RubyVM::InstructionSequence.compile_file(filepath)
542
+ run_top_frame(InstructionSequence.from(iseq.to_a))
543
+ end
544
+
545
+ def require_internal(filepath, loading: false)
546
+ case (extname = File.extname(filepath))
547
+ when ""
548
+ # search for all the extensions
549
+ searching = filepath
550
+ extensions = ["", ".rb", DLEXT, SOEXT]
551
+ when ".rb", DLEXT, SOEXT
552
+ # search only for the given extension name
553
+ searching = File.basename(filepath, extname)
554
+ extensions = [extname]
555
+ else
556
+ # we don't handle these extensions, raise a load error
557
+ raise LoadError, "cannot load such file -- #{filepath}"
558
+ end
559
+
560
+ if filepath.start_with?("/")
561
+ # absolute path, search only in the given directory
562
+ directories = [File.dirname(searching)]
563
+ searching = File.basename(searching)
564
+ else
565
+ # relative path, search in the load path
566
+ directories = $LOAD_PATH
567
+ end
568
+
569
+ directories.each do |directory|
570
+ extensions.each do |extension|
571
+ absolute_path = File.join(directory, "#{searching}#{extension}")
572
+ next unless File.exist?(absolute_path)
573
+
574
+ if !loading && $LOADED_FEATURES.include?(absolute_path)
575
+ return false
576
+ elsif extension == ".rb"
577
+ require_resolved(absolute_path)
578
+ return true
579
+ elsif loading
580
+ return Kernel.send(:yarv_load, filepath)
581
+ else
582
+ return Kernel.send(:yarv_require, filepath)
583
+ end
584
+ end
585
+ end
586
+
587
+ if loading
588
+ Kernel.send(:yarv_load, filepath)
589
+ else
590
+ Kernel.send(:yarv_require, filepath)
591
+ end
592
+ end
593
+
594
+ def require(filepath)
595
+ require_internal(filepath, loading: false)
596
+ end
597
+
598
+ def require_relative(filepath)
599
+ Kernel.yarv_require_relative(filepath)
600
+ end
601
+
602
+ def load(filepath)
603
+ require_internal(filepath, loading: true)
604
+ end
605
+
606
+ def eval(
607
+ source,
608
+ binding = TOPLEVEL_BINDING,
609
+ filename = "(eval)",
610
+ lineno = 1
611
+ )
612
+ Kernel.yarv_eval(source, binding, filename, lineno)
613
+ end
614
+
615
+ def throw(tag, value = nil)
616
+ Kernel.throw(tag, value)
617
+ end
618
+
619
+ def catch(tag, &block)
620
+ Kernel.catch(tag, &block)
621
+ end
622
+ end
623
+ end
624
+ end