Module: Subaltern
- Defined in:
- lib/subaltern.rb,
lib/subaltern/errors.rb,
lib/subaltern/kernel.rb,
lib/subaltern/context.rb,
lib/subaltern/version.rb,
lib/subaltern/evaluator.rb
Defined Under Namespace
Classes: AccessError, BlacklistedMethodError, Block, Command, Context, Function, LoopFunction, NonWhitelistedClassError, NonWhitelistedMethodError, Return, SubalternError, UndefinedVariableError
Constant Summary
collapse
- VERSION =
'1.0.0'
- BLACKLIST =
– whitelisting, and some blacklisting, out of pure distrust ++
%w[
extend instance_eval instance_exec method methods include
private_methods public_methods protected_methods singleton_methods
send __send__
taint tainted? untaint
instance_variables instance_variable_get instance_variable_set
instance_variable_defined?
free frozen?
]
- WHITELIST =
TODO :
eventually generate those whitelists by doing
class.public_instance_methods - BLACKLIST
though it would accept any new methods added by the ‘user’. Well, since the ‘user’ can’t overwrite methods…
{
Array => %w[
& * + - << <=> == === =~ [] []=
all? any? assoc at choice class clear clone collect collect! combination
compact compact! concat count cycle delete delete_at delete_if detect display
drop drop_while dup each each_cons each_index each_slice each_with_index
empty? entries enum_cons enum_for enum_slice enum_with_index eql? equal?
fetch fill find find_all find_index first flatten flatten!
grep group_by hash id include? index indexes indices inject insert inspect
instance_of? is_a? join kind_of? last length map map! max max_by member?
min min_by minmax minmax_by nil? nitems none? object_id one? pack partition
permutation pop product push rassoc reduce reject reject! replace respond_to?
reverse reverse! reverse_each rindex select shift shuffle shuffle! size
slice slice! sort sort! sort_by take take_while tap to_a to_ary to_enum to_s
transpose type uniq uniq! unshift untaint values_at zip |
],
Fixnum => %w[
% & * ** + +@ - -@ / < << <= <=> == === =~ > >= >> [] ^ __id__
abs between? ceil chr class clone coerce display div divmod downto dup enum_for
eql? equal? even? fdiv floor hash id id2name inspect
instance_of? integer? is_a? kind_of? modulo next nil? nonzero? object_id odd?
ord prec prec_f prec_i pred quo remainder respond_to? round size step succ tap
times to_a to_enum to_f to_i to_int to_s to_sym truncate type upto zero? | ~
],
Hash => %w[
== === =~ [] []= __id__
all? any? class clear clone collect count cycle default default= default_proc
delete delete_if detect display drop drop_while dup each each_cons each_key
each_pair each_slice each_value each_with_index empty? entries enum_cons
enum_for enum_slice enum_with_index eql? equal? fetch find find_all find_index
first grep group_by has_key? has_value? hash id include? index indexes indices
inject inspect instance_of? invert is_a? key? keys kind_of? length map max
max_by member? merge merge! min min_by minmax minmax_by nil? none? object_id
one? partition reduce rehash reject reject! replace respond_to? reverse_each
select shift size sort sort_by store take take_while tap to_a to_enum to_hash
to_s type update value? values values_at zip
],
MatchData => %[
[]
],
NilClass => %[
& == === =~ ^ __id__ class clone display dup enum_for eql? equal? hash
id inspect instance_of? is_a? kind_of? nil? object_id respond_to?
tap to_a to_enum to_f to_i to_s type |
],
Regexp => %w[
match
],
String => %w[
% * + < << <= <=> == === =~ > >= [] []= __id__
all? any? between? bytes bytesize capitalize capitalize! casecmp center chars
chomp chomp! chop chop! class clone collect concat count crypt cycle delete
delete! detect display downcase downcase! drop drop_while dump dup
each each_byte each_char each_cons each_line each_slice each_with_index empty?
end_with? entries enum_cons enum_for enum_slice enum_with_index eql? equal?
find find_all find_index first grep group_by gsub gsub! hash hex id include?
index inject insert inspect instance_of? intern is_a? kind_of? length lines
ljust lstrip lstrip! map match max max_by member? min min_by minmax minmax_by
next next! nil? none? object_id oct one? partition reduce reject replace
respond_to? reverse reverse! reverse_each rindex rjust rpartition rstrip
rstrip! scan select size slice slice! sort sort_by split squeeze squeeze!
start_with? strip strip! sub sub! succ succ! sum swapcase swapcase! take
take_while tap to_a to_enum to_f to_i to_s to_str to_sym tr tr! tr_s tr_s!
type unpack upcase upcase! upto zip
],
Class => %w[
===
],
TrueClass => %w[
==
],
FalseClass => %w[
==
],
}
- WHITELISTED_CONSTANTS =
WHITELIST.keys.collect { |k| k.name.to_sym } +
[ :FalseClass, :TrueClass ]
Class Method Summary
collapse
-
.bounce(target, method) ⇒ Object
Will raise an exception if calling that method on this target is not whitelisted (or is blacklisted).
-
.eval(source, vars = {}) ⇒ Object
-
.eval_and(context, tree) ⇒ Object
-
.eval_array(context, tree) ⇒ Object
-
.eval_block(context, tree) ⇒ Object
-
.eval_break(context, tree) ⇒ Object
-
.eval_call(context, tree) ⇒ Object
-
.eval_case(context, tree) ⇒ Object
-
.eval_colon2(context, tree) ⇒ Object
-
.eval_const(context, tree) ⇒ Object
-
.eval_defn(context, tree) ⇒ Object
-
.eval_dstr(context, tree) ⇒ Object
-
.eval_dxstr(context, tree) ⇒ Object
-
.eval_evstr(context, tree) ⇒ Object
-
.eval_for(context, tree) ⇒ Object
-
.eval_gvar(context, tree) ⇒ Object
-
.eval_hash(context, tree) ⇒ Object
-
.eval_if(context, tree) ⇒ Object
-
.eval_iter(context, tree) ⇒ Object
-
.eval_lasgn(context, tree) ⇒ Object
-
.eval_lit(context, tree) ⇒ Object
-
.eval_lvar(context, tree) ⇒ Object
-
.eval_next(context, tree) ⇒ Object
-
.eval_not(context, tree) ⇒ Object
-
.eval_or(context, tree) ⇒ Object
-
.eval_rescue(context, tree) ⇒ Object
-
.eval_return(context, tree) ⇒ Object
-
.eval_scope(context, tree) ⇒ Object
-
.eval_str(context, tree) ⇒ Object
-
.eval_tree(context, tree) ⇒ Object
-
.eval_until(context, tree) ⇒ Object
-
.eval_while(context, tree) ⇒ Object
-
.eval_xstr(context, tree) ⇒ Object
-
.eval_yield(context, tree) ⇒ Object
-
.is_javascripty_hash_lookup?(target, method, args) ⇒ Boolean
-
.kernel ⇒ Object
Class Method Details
.bounce(target, method) ⇒ Object
Will raise an exception if calling that method on this target is not whitelisted (or is blacklisted).
.eval(source, vars = {}) ⇒ Object
34
35
36
37
|
# File 'lib/subaltern.rb', line 34
def self.eval(source, vars={})
Context.new(nil, vars).eval(source)
end
|
.eval_and(context, tree) ⇒ Object
514
515
516
517
518
519
520
521
522
523
524
|
# File 'lib/subaltern/evaluator.rb', line 514
def self.eval_and(context, tree)
tree[1..-1].inject(nil) { |_, t|
current = eval_tree(context, t)
return current unless current
current
}
end
|
.eval_array(context, tree) ⇒ Object
340
341
342
343
|
# File 'lib/subaltern/evaluator.rb', line 340
def self.eval_array(context, tree)
tree[1..-1].collect { |t| eval_tree(context, t) }
end
|
.eval_block(context, tree) ⇒ Object
386
387
388
389
|
# File 'lib/subaltern/evaluator.rb', line 386
def self.eval_block(context, tree)
tree[1..-1].collect { |t| eval_tree(context, t) }.last
end
|
.eval_break(context, tree) ⇒ Object
485
486
487
488
489
|
# File 'lib/subaltern/evaluator.rb', line 485
def self.eval_break(context, tree)
raise(
Command.new('break', tree[1..-1].collect { |t| eval_tree(context, t) }))
end
|
.eval_call(context, tree) ⇒ Object
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
|
# File 'lib/subaltern/evaluator.rb', line 358
def self.eval_call(context, tree)
if tree[1] == nil
value = eval_lvar(context, tree[1, 2])
return value.call(context, tree) if value.is_a?(Subaltern::Function)
return value
end
target = eval_tree(context, tree[1])
method = tree[2].to_s
args = tree[3][1..-1]
if is_javascripty_hash_lookup?(target, method, args)
return target[method]
end
args = args.collect { |t| eval_tree(context, t) }
return target.call(context, args) if target.is_a?(Subaltern::Block)
bounce(target, method)
target.send(method, *args)
end
|
.eval_case(context, tree) ⇒ Object
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
|
# File 'lib/subaltern/evaluator.rb', line 531
def self.eval_case(context, tree)
value = eval_tree(context, tree[1])
whens = tree[2..-1].select { |t| t[0] == :when }
the_else = (tree[2..-1] - whens).first
whens.each do |w|
eval_tree(context, w[1]).each do |v|
return eval_tree(context, w[2]) if v === value
end
end
the_else ? eval_tree(context, the_else) : nil
end
|
.eval_colon2(context, tree) ⇒ Object
406
407
408
409
|
# File 'lib/subaltern/evaluator.rb', line 406
def self.eval_colon2(context, tree)
raise AccessError.new("no access to constants (#{tree[1]})")
end
|
.eval_const(context, tree) ⇒ Object
411
412
413
414
415
416
417
418
|
# File 'lib/subaltern/evaluator.rb', line 411
def self.eval_const(context, tree)
if WHITELISTED_CONSTANTS.include?(tree[1])
return Kernel.const_get(tree[1].to_s)
end
raise AccessError.new("no access to constants (#{tree[1]})")
end
|
.eval_defn(context, tree) ⇒ Object
420
421
422
423
424
425
426
|
# File 'lib/subaltern/evaluator.rb', line 420
def self.eval_defn(context, tree)
name = tree[1].to_s
context[name] = Function.new(tree)
nil
end
|
.eval_dstr(context, tree) ⇒ Object
318
319
320
321
322
323
|
# File 'lib/subaltern/evaluator.rb', line 318
def self.eval_dstr(context, tree)
tree[1..-1].collect { |t|
t.is_a?(String) ? t : eval_tree(context, t)
}.join
end
|
.eval_dxstr(context, tree) ⇒ Object
335
336
337
338
|
# File 'lib/subaltern/evaluator.rb', line 335
def self.eval_dxstr(context, tree)
raise AccessError.new("no backquoting allowed (#{tree[1]})")
end
|
.eval_evstr(context, tree) ⇒ Object
325
326
327
328
|
# File 'lib/subaltern/evaluator.rb', line 325
def self.eval_evstr(context, tree)
eval_tree(context, tree[1])
end
|
.eval_for(context, tree) ⇒ Object
584
585
586
587
588
589
590
|
# File 'lib/subaltern/evaluator.rb', line 584
def self.eval_for(context, tree)
values = eval_tree(context, tree[1])
block = Block.new(tree[2..-1])
values.each { |v| block.call(context, [ v ], false) }
end
|
.eval_gvar(context, tree) ⇒ Object
401
402
403
404
|
# File 'lib/subaltern/evaluator.rb', line 401
def self.eval_gvar(context, tree)
raise AccessError.new("no global variables (#{tree[1]})")
end
|
.eval_hash(context, tree) ⇒ Object
345
346
347
348
|
# File 'lib/subaltern/evaluator.rb', line 345
def self.eval_hash(context, tree)
Hash[*eval_array(context, tree)]
end
|
.eval_if(context, tree) ⇒ Object
497
498
499
500
501
502
503
504
|
# File 'lib/subaltern/evaluator.rb', line 497
def self.eval_if(context, tree)
if eval_tree(context, tree[1])
eval_tree(context, tree[2])
elsif tree[3]
eval_tree(context, tree[3])
end
end
|
.eval_iter(context, tree) ⇒ Object
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
|
# File 'lib/subaltern/evaluator.rb', line 438
def self.eval_iter(context, tree)
if tree[1][1] == nil
con = Context.new(context, '__block' => Block.new(tree[2..-1]))
eval_call(con, tree[1])
else
target = eval_tree(context, tree[1][1])
method = tree[1][2]
bounce(target, method)
method_args = tree[1][3][1..-1].collect { |t| eval_tree(context, t) }
block = Block.new(tree[2..-1])
command = nil
result = target.send(method, *method_args) { |*args|
begin
block.call(context, args)
rescue Command => c
case c.name
when 'break' then break c
when 'next' then next c
else raise c
end
end
}
Command.narrow(result)
end
end
|
.eval_lasgn(context, tree) ⇒ Object
391
392
393
394
|
# File 'lib/subaltern/evaluator.rb', line 391
def self.eval_lasgn(context, tree)
context[tree[1].to_s] = eval_tree(context, tree[2])
end
|
.eval_lit(context, tree) ⇒ Object
308
309
310
311
|
# File 'lib/subaltern/evaluator.rb', line 308
def self.eval_lit(context, tree)
tree[1]
end
|
.eval_lvar(context, tree) ⇒ Object
396
397
398
399
|
# File 'lib/subaltern/evaluator.rb', line 396
def self.eval_lvar(context, tree)
context[tree[1].to_s]
end
|
.eval_next(context, tree) ⇒ Object
491
492
493
494
495
|
# File 'lib/subaltern/evaluator.rb', line 491
def self.eval_next(context, tree)
raise(
Command.new('next', tree[1..-1].collect { |t| eval_tree(context, t) }))
end
|
.eval_not(context, tree) ⇒ Object
526
527
528
529
|
# File 'lib/subaltern/evaluator.rb', line 526
def self.eval_not(context, tree)
not eval_tree(context, tree[1])
end
|
.eval_or(context, tree) ⇒ Object
506
507
508
509
510
511
512
|
# File 'lib/subaltern/evaluator.rb', line 506
def self.eval_or(context, tree)
tree[1..-1].each { |t|
result = eval_tree(context, t)
return result if result
}
end
|
.eval_rescue(context, tree) ⇒ Object
592
593
594
595
|
# File 'lib/subaltern/evaluator.rb', line 592
def self.eval_rescue(context, tree)
Subaltern.eval_tree(context, tree[2][2])
end
|
.eval_return(context, tree) ⇒ Object
433
434
435
436
|
# File 'lib/subaltern/evaluator.rb', line 433
def self.eval_return(context, tree)
raise(Return.new(eval_tree(context, tree[1])))
end
|
.eval_scope(context, tree) ⇒ Object
428
429
430
431
|
# File 'lib/subaltern/evaluator.rb', line 428
def self.eval_scope(context, tree)
eval_tree(context, tree[1])
end
|
.eval_str(context, tree) ⇒ Object
313
314
315
316
|
# File 'lib/subaltern/evaluator.rb', line 313
def self.eval_str(context, tree)
tree[1]
end
|
.eval_tree(context, tree) ⇒ Object
291
292
293
294
295
296
297
298
299
300
301
302
|
# File 'lib/subaltern/evaluator.rb', line 291
def self.eval_tree(context, tree)
send("eval_#{tree.first}", context, tree)
rescue NoMethodError => nme
puts '-' * 80
p nme
p tree
puts nme.backtrace.join("\n")
puts '-' * 80
raise nme
end
|
.eval_until(context, tree) ⇒ Object
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
|
# File 'lib/subaltern/evaluator.rb', line 566
def self.eval_until(context, tree)
result = until eval_tree(context, tree[1])
begin
eval_tree(context, tree[2])
rescue Command => c
case c.name
when 'break' then break *c.args
when 'next' then next *c.args
else raise c
end
end
end
Command.narrow(result)
end
|
.eval_while(context, tree) ⇒ Object
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
|
# File 'lib/subaltern/evaluator.rb', line 548
def self.eval_while(context, tree)
result = while eval_tree(context, tree[1])
begin
eval_tree(context, tree[2])
rescue Command => c
case c.name
when 'break' then break c
when 'next' then next c
else raise c
end
end
end
Command.narrow(result)
end
|
.eval_xstr(context, tree) ⇒ Object
330
331
332
333
|
# File 'lib/subaltern/evaluator.rb', line 330
def self.eval_xstr(context, tree)
raise AccessError.new("no backquoting allowed (#{tree[1]})")
end
|
.eval_yield(context, tree) ⇒ Object
478
479
480
481
482
483
|
# File 'lib/subaltern/evaluator.rb', line 478
def self.eval_yield(context, tree)
args = tree[1..-1].collect { |t| eval_tree(context, t) }
context['__block'].call(context, args)
end
|
.is_javascripty_hash_lookup?(target, method, args) ⇒ Boolean
350
351
352
353
354
355
356
|
# File 'lib/subaltern/evaluator.rb', line 350
def self.is_javascripty_hash_lookup?(target, method, args)
target.is_a?(Hash) &&
args.empty? &&
( ! WHITELIST[Hash].include?(method)) &&
target.has_key?(method)
end
|
.kernel ⇒ Object
26
27
28
29
30
31
|
# File 'lib/subaltern/kernel.rb', line 26
def self.kernel
{
'loop' => LoopFunction.new
}
end
|