serializable_proc 0.1.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.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/HISTORY.txt +4 -0
- data/LICENSE +20 -0
- data/README.rdoc +132 -0
- data/Rakefile +136 -0
- data/VERSION +1 -0
- data/lib/serializable_proc/binding.rb +53 -0
- data/lib/serializable_proc/marshalable.rb +51 -0
- data/lib/serializable_proc/parsers/pt.rb +16 -0
- data/lib/serializable_proc/parsers/rp.rb +93 -0
- data/lib/serializable_proc/parsers.rb +8 -0
- data/lib/serializable_proc/sandboxer.rb +24 -0
- data/lib/serializable_proc.rb +169 -0
- data/spec/extracting_bound_variables_spec.rb +99 -0
- data/spec/initializing_errors_spec.rb +95 -0
- data/spec/marshalling_spec.rb +51 -0
- data/spec/multiple_arities_serializable_proc_spec.rb +159 -0
- data/spec/one_arity_serializable_proc_spec.rb +159 -0
- data/spec/optional_arity_serializable_proc_spec.rb +160 -0
- data/spec/proc_like_spec.rb +191 -0
- data/spec/spec_helper.rb +100 -0
- data/spec/zero_arity_serializable_proc_spec.rb +159 -0
- metadata +127 -0
data/.document
ADDED
data/.gitignore
ADDED
data/HISTORY.txt
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 NgTzeYang
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
= serializable_proc
|
2
|
+
|
3
|
+
As the name suggests, SerializableProc is a proc that can be serialized (marshalled).
|
4
|
+
A proc is a closure, which consists of the code block defining it, and binding of
|
5
|
+
local variables. SerializableProc's approach to serializability is to extract:
|
6
|
+
|
7
|
+
1. the code from the proc (using ParseTree or RubyParser), and
|
8
|
+
2. the local, instance, class & global variables reference within the proc from the
|
9
|
+
proc's binding, using deep copy via Marshal.load(Marshal.dump(var))
|
10
|
+
|
11
|
+
A SerializableProc differs from the vanilla Proc in the following 2 ways:
|
12
|
+
|
13
|
+
=== 1. Isolated variables
|
14
|
+
|
15
|
+
Upon initializing, all variables (local, instance, class & global) within its context
|
16
|
+
are extracted from the proc's binding, and are isolated from changes outside the proc's
|
17
|
+
scope, thus, achieving a snapshot effect.
|
18
|
+
|
19
|
+
require 'rubygems'
|
20
|
+
require 'serializable_proc'
|
21
|
+
|
22
|
+
x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
|
23
|
+
|
24
|
+
s_proc = SerializableProc.new { [x, @x, @@x, $x].join(', ') }
|
25
|
+
v_proc = Proc.new { [x, @x, @@x, $x].join(', ') }
|
26
|
+
|
27
|
+
x, @x, @@x, $x = 'ly', 'iy', 'cy', 'gy'
|
28
|
+
|
29
|
+
s_proc.call # >> "lx, ix, cx, gx"
|
30
|
+
v_proc.call # >> "ly, iy, cy, gy"
|
31
|
+
|
32
|
+
=== 2. Marshallable
|
33
|
+
|
34
|
+
No throwing of TypeError when marshalling a SerializableProc:
|
35
|
+
|
36
|
+
Marshal.load(Marshal.dump(s_proc)).call # >> "lx, ix, cx, gx"
|
37
|
+
Marshal.load(Marshal.dump(v_proc)).call # >> TypeError (cannot dump Proc)
|
38
|
+
|
39
|
+
== Installing It
|
40
|
+
|
41
|
+
The religiously standard way:
|
42
|
+
|
43
|
+
$ gem install ParseTree serializable_proc
|
44
|
+
|
45
|
+
Or on 1.9.* or JRuby:
|
46
|
+
|
47
|
+
$ gem install ruby_parser serializable_proc
|
48
|
+
|
49
|
+
By default, SerializableProc attempts to load ParseTree, which supports better
|
50
|
+
performance & offers many dynamic goodness. If ParseTree cannot be found,
|
51
|
+
SerializableProc falls back to the RubyParser-based which suffers some gotchas due
|
52
|
+
to its static analysis nature (see 'Gotchas' section).
|
53
|
+
|
54
|
+
== Performance
|
55
|
+
|
56
|
+
SerializableProc relies on ParseTree or RubyParser to do code extraction. While running
|
57
|
+
in ParseTree mode, thanks to the goodness of dynamic code analysis, SerializableProc
|
58
|
+
performs faster by a magnitude of abt 7.5 times for the same ruby, as illustrated with
|
59
|
+
the following benchmark results (obtained from running the specs suite):
|
60
|
+
|
61
|
+
MRI & implementation user system total real
|
62
|
+
1.8.7p299 (ParseTree) 0.000000 0.000000 1.310000 1.312676
|
63
|
+
1.8.7p299 (RubyParser) 0.000000 0.010000 9.560000 9.706455
|
64
|
+
1.9.1p376 (RubyParser) 0.010000 0.010000 8.240000 8.288799
|
65
|
+
|
66
|
+
(the above is run on my x86_64-linux):
|
67
|
+
|
68
|
+
== Gotchas
|
69
|
+
|
70
|
+
As RubyParser does only static code analysis, quite a bit of regexp matchings are needed
|
71
|
+
to get SerializableProc to work in RubyParser mode. However, as our regexp kungfu is not
|
72
|
+
perfect (yet), pls take note of the following:
|
73
|
+
|
74
|
+
=== 1. Cannot have multiple initializing code block per line
|
75
|
+
|
76
|
+
The following initializations throw SerializableProc::CannotAnalyseCodeError:
|
77
|
+
|
78
|
+
# Multiple SerializableProc.new per line
|
79
|
+
SerializableProc.new { x } ; SerializableProc.new { y }
|
80
|
+
|
81
|
+
# Multiple lambda per line (the same applies to proc & Proc.new)
|
82
|
+
x_proc = lambda { x } ; y_proc = lambda { y }
|
83
|
+
SerializableProc.new(&x_proc)
|
84
|
+
|
85
|
+
# Mixed lambda, proc & Proc.new per line
|
86
|
+
x_proc = proc { x } ; y_proc = lambda { y }
|
87
|
+
SerializableProc.new(&x_proc)
|
88
|
+
|
89
|
+
=== 2. Limited ways to initialize code blocks
|
90
|
+
|
91
|
+
Code block must be initialized with lambda, proc, Proc.new & SerializableProc.new,
|
92
|
+
the following will throw SerializableProc::CannotAnalyseCodeError:
|
93
|
+
|
94
|
+
def create_serializable_proc(&block)
|
95
|
+
SerializableProc.new(&block)
|
96
|
+
end
|
97
|
+
|
98
|
+
create_serializable_proc { x }
|
99
|
+
|
100
|
+
But the following will work as expected:
|
101
|
+
|
102
|
+
x_proc = lambda { x }
|
103
|
+
create_serializable_proc(&x_proc)
|
104
|
+
|
105
|
+
== TODO (just brain-dumping)
|
106
|
+
|
107
|
+
1. Provide flexibility for user to specify whether global variables should be isolated,
|
108
|
+
because globals are globals, it may sometimes be useful to use/manipulate the globals
|
109
|
+
within the context that the SerializableProc is called, instead of when it is
|
110
|
+
initialized
|
111
|
+
2. The RubyParser-based implementation probably need alot more optimization to catch up
|
112
|
+
on ParseTree-based one
|
113
|
+
3. Implementing alternative means of extracting the code block without requiring help
|
114
|
+
of ParseTree or RubyParser
|
115
|
+
4. Implement workaround to tackle line-numbering bug in JRuby, which causes the
|
116
|
+
RubyParser-based implementation to fail, for more info abt JRuby's line-numbering
|
117
|
+
bug, see http://stackoverflow.com/questions/3454838/jruby-line-numbering-problem
|
118
|
+
|
119
|
+
== Note on Patches/Pull Requests
|
120
|
+
|
121
|
+
* Fork the project.
|
122
|
+
* Make your feature addition or bug fix.
|
123
|
+
* Add tests for it. This is important so I don't break it in a
|
124
|
+
future version unintentionally.
|
125
|
+
* Commit, do not mess with rakefile, version, or history.
|
126
|
+
(if you want to have your own version, that is fine but bump version in a commit by
|
127
|
+
itself I can ignore when I pull)
|
128
|
+
* Send me a pull request. Bonus points for topic branches.
|
129
|
+
|
130
|
+
== Copyright
|
131
|
+
|
132
|
+
Copyright (c) 2010 NgTzeYang. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
8
|
+
gem.name = "serializable_proc"
|
9
|
+
gem.summary = %Q{Proc that can be serialized (as the name suggests)}
|
10
|
+
gem.description = %Q{
|
11
|
+
Give & take, serializing a ruby proc is possible, though not a perfect one.
|
12
|
+
Requires either ParseTree (faster) or RubyParser (& Ruby2Ruby).
|
13
|
+
}
|
14
|
+
gem.email = "ngty77@gmail.com"
|
15
|
+
gem.homepage = "http://github.com/ngty/serializable_proc"
|
16
|
+
gem.authors = ["NgTzeYang"]
|
17
|
+
gem.add_dependency "ruby2ruby", ">= 1.2.4"
|
18
|
+
gem.add_development_dependency "bacon", ">= 0"
|
19
|
+
# Plus one of the following groups:
|
20
|
+
#
|
21
|
+
# 1). ParseTree (better performance + dynamic goodness, but not supported on java & 1.9.*)
|
22
|
+
# >> gem.add_dependency "ParseTree", ">= 3.0.5"
|
23
|
+
#
|
24
|
+
# 2). RubyParser (supported for all)
|
25
|
+
# >> gem.add_dependency "ruby_parser", ">= 2.0.4"
|
26
|
+
|
27
|
+
unless RUBY_PLATFORM =~ /java/i or RUBY_VERSION.include?('1.9.')
|
28
|
+
gem.post_install_message = %Q{
|
29
|
+
/////////////////////////////////////////////////////////////////////////////////
|
30
|
+
|
31
|
+
** SerializableProc **
|
32
|
+
|
33
|
+
You are installing SerializableProc on a ruby platform & version that supports
|
34
|
+
ParseTree. With ParseTree, u can enjoy better performance of SerializableProc,
|
35
|
+
as well as other dynamic code analysis goodness, as compared to the default
|
36
|
+
implementation using RubyParser's less flexible static code analysis.
|
37
|
+
|
38
|
+
Anyway, u have been informed, SerializableProc will fallback on its default
|
39
|
+
implementation using RubyParser.
|
40
|
+
|
41
|
+
/////////////////////////////////////////////////////////////////////////////////
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
Jeweler::GemcutterTasks.new
|
47
|
+
rescue LoadError
|
48
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
49
|
+
end
|
50
|
+
|
51
|
+
require 'rake/testtask'
|
52
|
+
Rake::TestTask.new(:spec) do |spec|
|
53
|
+
spec.libs << 'lib' << 'spec'
|
54
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
55
|
+
spec.verbose = true
|
56
|
+
end
|
57
|
+
|
58
|
+
begin
|
59
|
+
require 'rcov/rcovtask'
|
60
|
+
Rcov::RcovTask.new do |spec|
|
61
|
+
spec.libs << 'spec'
|
62
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
63
|
+
spec.verbose = true
|
64
|
+
end
|
65
|
+
rescue LoadError
|
66
|
+
task :rcov do
|
67
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
task :spec => :check_dependencies
|
72
|
+
|
73
|
+
begin
|
74
|
+
require 'reek/adapters/rake_task'
|
75
|
+
Reek::RakeTask.new do |t|
|
76
|
+
t.fail_on_error = true
|
77
|
+
t.verbose = false
|
78
|
+
t.source_files = 'lib/**/*.rb'
|
79
|
+
end
|
80
|
+
rescue LoadError
|
81
|
+
task :reek do
|
82
|
+
abort "Reek is not available. In order to run reek, you must: sudo gem install reek"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
begin
|
87
|
+
require 'roodi'
|
88
|
+
require 'roodi_task'
|
89
|
+
RoodiTask.new do |t|
|
90
|
+
t.verbose = false
|
91
|
+
end
|
92
|
+
rescue LoadError
|
93
|
+
task :roodi do
|
94
|
+
abort "Roodi is not available. In order to run roodi, you must: sudo gem install roodi"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
task :default => :spec
|
99
|
+
|
100
|
+
require 'rake/rdoctask'
|
101
|
+
Rake::RDocTask.new do |rdoc|
|
102
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
103
|
+
|
104
|
+
rdoc.rdoc_dir = 'rdoc'
|
105
|
+
rdoc.title = "serializable_proc #{version}"
|
106
|
+
rdoc.rdoc_files.include('README*')
|
107
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
108
|
+
end
|
109
|
+
|
110
|
+
# Benchmarking
|
111
|
+
task :benchmark, :task, :times do |t, args|
|
112
|
+
times, task = (args.times || 5).to_i.method(:times), args.task
|
113
|
+
title = " ~ Benchmark Results for Task :#{task} ~ "
|
114
|
+
results = [%w{nth}, %w{user}, %w{system}, %w{total}, %w{real}]
|
115
|
+
|
116
|
+
# Running benchmarking & collecting results
|
117
|
+
require 'benchmark'
|
118
|
+
times.call do |i|
|
119
|
+
result = Benchmark.measure{ Rake::Task[task].execute }.to_s
|
120
|
+
user, system, total, real =
|
121
|
+
result.match(/^\s*(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+\(\s*(\d+\.\d+)\)$/)[1..-1]
|
122
|
+
["##{i.succ}", user, system, total, real].each_with_index{|val, j| results[j] << val }
|
123
|
+
end
|
124
|
+
|
125
|
+
# Formatting benchmarking results
|
126
|
+
formatted_results = results.map do |rs|
|
127
|
+
width = rs.map(&:to_s).map(&:size).max
|
128
|
+
rs.map{|r| ' ' + r.ljust(width, ' ') }
|
129
|
+
end.transpose.map{|row| row.join }
|
130
|
+
|
131
|
+
# Showdown .. printout
|
132
|
+
line = '=' * ([title.size, formatted_results.map(&:size).max].max + 2)
|
133
|
+
puts [line, title, formatted_results.join("\n"), line].join("\n\n")
|
134
|
+
|
135
|
+
end
|
136
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class SerializableProc
|
2
|
+
|
3
|
+
class CannotSerializeVariableError < Exception ; end
|
4
|
+
|
5
|
+
class Binding
|
6
|
+
|
7
|
+
include Marshalable
|
8
|
+
marshal_attr :vars
|
9
|
+
|
10
|
+
def initialize(binding, sexp)
|
11
|
+
@vars, sexp_str = {}, sexp.inspect
|
12
|
+
while m = sexp_str.match(/^(.*?s\(:(?:l|g|c|i)var, :([^\)]+)\))/)
|
13
|
+
ignore, var = m[1..2]
|
14
|
+
sexp_str.sub!(ignore,'')
|
15
|
+
begin
|
16
|
+
val = binding.eval(var) rescue nil
|
17
|
+
@vars.update(Sandboxer.fvar(var) => mclone(val))
|
18
|
+
rescue TypeError
|
19
|
+
raise CannotSerializeVariableError.new("Variable #{var} cannot be serialized !!")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def eval!
|
25
|
+
@binding ||= (
|
26
|
+
set_vars = @vars.map{|(k,v)| "#{k} = Marshal.load(%|#{mdump(v)}|)" } * '; '
|
27
|
+
(binding = Kernel.binding).eval(set_vars)
|
28
|
+
binding.extend(Extensions)
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
module Extensions
|
33
|
+
def self.extended(base)
|
34
|
+
class << base
|
35
|
+
|
36
|
+
alias_method :orig_eval, :eval
|
37
|
+
|
38
|
+
def eval(str)
|
39
|
+
begin
|
40
|
+
@fvar = Sandboxer.fvar(str).to_s
|
41
|
+
orig_eval(@fvar)
|
42
|
+
rescue NameError => e
|
43
|
+
msg = e.message.sub(@fvar, str)
|
44
|
+
raise NameError.new(msg)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class SerializableProc
|
2
|
+
module Marshalable
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
extend ClassMethods
|
7
|
+
include InstanceMethods
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def marshal_attrs(*attrs)
|
16
|
+
attrs = attrs.map{|attr| :"@#{attr}" }
|
17
|
+
self.class_eval do
|
18
|
+
define_method(:marshalable_attrs) { attrs }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
alias_method :marshal_attr, :marshal_attrs
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
module InstanceMethods
|
27
|
+
|
28
|
+
def marshal_dump
|
29
|
+
marshalable_attrs.map{|attr| instance_variable_get(attr) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def marshal_load(data)
|
33
|
+
[data].flatten.each_with_index do |val, i|
|
34
|
+
instance_variable_set(marshalable_attrs[i], val)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def mdump(val)
|
41
|
+
Marshal.dump(val).gsub('|','\|')
|
42
|
+
end
|
43
|
+
|
44
|
+
def mclone(val)
|
45
|
+
Marshal.load(mdump(val))
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class SerializableProc
|
2
|
+
module Parsers
|
3
|
+
module PT
|
4
|
+
class << self
|
5
|
+
def process(block)
|
6
|
+
if Object.const_defined?(:ParseTree)
|
7
|
+
sexp = block.to_sexp
|
8
|
+
runnable_code = RUBY_2_RUBY.process(Sandboxer.fsexp(sexp))
|
9
|
+
extracted_code = RUBY_2_RUBY.process(eval(sexp.inspect))
|
10
|
+
[{:runnable => runnable_code, :extracted => extracted_code}, sexp]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
class SerializableProc
|
2
|
+
|
3
|
+
class CannotAnalyseCodeError < Exception ; end
|
4
|
+
|
5
|
+
module Parsers
|
6
|
+
module RP
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def process(klass, file, line)
|
10
|
+
const_set(:RUBY_PARSER, RubyParser.new) unless const_defined?(:RUBY_PARSER)
|
11
|
+
@klass, @file, @line = klass, file, line
|
12
|
+
extract_code_and_sexp
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def extract_code_and_sexp
|
18
|
+
sexp_str, remaining, type, marker = extract_sexp_args
|
19
|
+
while frag = remaining[/^([^\)]*\))/,1]
|
20
|
+
begin
|
21
|
+
sexp = eval(sexp_str += frag) # this throws SyntaxError if sexp is invalid
|
22
|
+
runnable_code, extracted_code = [
|
23
|
+
RUBY_2_RUBY.process(Sandboxer.fsexp(sexp)),
|
24
|
+
RUBY_2_RUBY.process(eval(sexp.inspect))
|
25
|
+
].map do |code|
|
26
|
+
unescape_magic_vars(code).sub(/#{marker}\s*;?/m,'').sub(type,'lambda')
|
27
|
+
end
|
28
|
+
return [{:runnable => runnable_code, :extracted => extracted_code}, sexp]
|
29
|
+
rescue SyntaxError
|
30
|
+
remaining.sub!(frag,'')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def extract_sexp_args
|
36
|
+
raw, type, marker = raw_sexp_and_marker
|
37
|
+
rq = lambda{|s| Regexp.quote(s) }
|
38
|
+
regexp = Regexp.new([
|
39
|
+
'^(.*(', (
|
40
|
+
case type
|
41
|
+
when /(#{@klass}|Proc)/ then rq["s(:iter, s(:call, s(:const, :#{$1}), :new, s(:arglist)),"]
|
42
|
+
else rq['s(:iter, s(:call, nil, :'] + '(?:proc|lambda)' + rq[', s(:arglist']
|
43
|
+
end
|
44
|
+
),
|
45
|
+
'.*?',
|
46
|
+
rq["s(:call, nil, :#{marker}, s(:arglist))"],
|
47
|
+
'))(.*)$'
|
48
|
+
].join, Regexp::MULTILINE)
|
49
|
+
[raw.match(regexp)[2..3], type, marker].flatten
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def raw_sexp_and_marker
|
54
|
+
%W{#{@klass}\.new lambda|proc|Proc\.new}.each do |declarative|
|
55
|
+
regexp = /^(.*?(#{declarative})\s*(do|\{)\s*(\|([^\|]*)\|\s*)?)/
|
56
|
+
raw = raw_code
|
57
|
+
lines1, lines2 = [(0 .. (@line - 2)), (@line.pred .. -1)].map{|r| raw[r] }
|
58
|
+
match, type = lines2[0].match(regexp)[1..2] rescue next
|
59
|
+
|
60
|
+
if lines2[0] =~ /^(.*?\W)?(#{declarative})(\W.*?\W(#{declarative}))+(\W.*)?$/
|
61
|
+
msg = "Static code analysis can only handle single occurrence of '%s' per line !!" %
|
62
|
+
declarative.split('|').join("'/'")
|
63
|
+
raise CannotAnalyseCodeError.new(msg)
|
64
|
+
elsif lines2[0] =~ /^(.*?\W)?(#{declarative})(\W.*)?$/
|
65
|
+
marker = "__serializable_proc_marker_#{@line}__"
|
66
|
+
lines = lines1.join + escape_magic_vars(lines2[0].sub(match, match+marker+';') + lines2[1..-1].join)
|
67
|
+
return [RUBY_PARSER.parse(lines).inspect, type, marker]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
raise CannotAnalyseCodeError.new('Cannot find specified initializer !!')
|
71
|
+
end
|
72
|
+
|
73
|
+
def escape_magic_vars(s)
|
74
|
+
%w{__FILE__ __LINE__}.inject(s) do |s, var|
|
75
|
+
s.gsub(var, "__serializable_proc_#{var.downcase}__")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def unescape_magic_vars(s)
|
80
|
+
%w{__FILE__ __LINE__}.inject(s) do |s, var|
|
81
|
+
s.gsub("__serializable_proc_#{var.downcase}__", var)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def raw_code
|
86
|
+
File.readlines(@file)
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class SerializableProc
|
2
|
+
module Sandboxer
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def fsexp(sexp)
|
6
|
+
n_sexp, t_sexp = nil, sexp.inspect
|
7
|
+
while m = t_sexp.match(/^(.*?s\(:)((i|l|c|g)(asgn|var|vdecl))(,\ :)((|@|@@|\$)([\w]+))(\)|,)/)
|
8
|
+
orig, prepend, _, type, declare, join, _, _, name, append = m[0..-1]
|
9
|
+
declare.sub!('vdecl','asgn')
|
10
|
+
n_sexp = "#{n_sexp}#{prepend}l#{declare}#{join}#{type}var_#{name}#{append}"
|
11
|
+
t_sexp.sub!(orig,'')
|
12
|
+
end
|
13
|
+
eval(n_sexp ? "#{n_sexp}#{t_sexp}" : sexp.inspect)
|
14
|
+
end
|
15
|
+
|
16
|
+
def fvar(var)
|
17
|
+
@translate_var_maps ||= {'@' => 'ivar_', '@@' => 'cvar_', '$' => 'gvar_', '' => 'lvar_'}
|
18
|
+
m = var.to_s.match(/^(|@|@@|\$)(\w+)$/)
|
19
|
+
var.to_s.sub(m[1], @translate_var_maps[m[1]]).to_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|