reflekt 0.7.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/Execution.rb +45 -0
- data/lib/Reflection.rb +145 -0
- data/lib/ShadowStack.rb +56 -0
- data/lib/reflekt.rb +83 -152
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e4d3c0c32672fd8813482a3f546032904d3d6f65e592fd4435eb7ada2bb1b201
|
4
|
+
data.tar.gz: d48f2b100986c4408db5cb055f4ebf1e6a50d99fa99dc6d98e8af8e5d8bcd66e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1422110bc34f7b88f8b4ba71359c408d38f3ebf72235735e51864fd0bd16c32fd5abc20e5cfa8a516478932800590f80f7f1b7b4d0dac3c0ebd18a3d8dfffd54
|
7
|
+
data.tar.gz: 4019f5868ca20d1aebfa9e4ddb2c5136782aa52185588cb44558f4725f51ea59f90bc75896e8bfac383151d7346bb534d98bc00a11a569c350d0c460fe3b31ab
|
data/lib/Execution.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
class Execution
|
2
|
+
|
3
|
+
attr_accessor :object
|
4
|
+
attr_accessor :caller_id
|
5
|
+
attr_accessor :caller_class
|
6
|
+
attr_accessor :parent
|
7
|
+
attr_accessor :child
|
8
|
+
attr_accessor :reflections
|
9
|
+
attr_accessor :is_reflecting
|
10
|
+
|
11
|
+
def initialize(object, reflection_count)
|
12
|
+
|
13
|
+
@object = object
|
14
|
+
@caller_id = object.object_id
|
15
|
+
@caller_class = object.class
|
16
|
+
@parent = nil
|
17
|
+
@child = nil
|
18
|
+
|
19
|
+
@reflections = Array.new(reflection_count)
|
20
|
+
@is_reflecting = false
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_empty_reflections?
|
25
|
+
@reflections.include? nil
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Is the Execution currently reflecting methods?
|
30
|
+
##
|
31
|
+
def is_reflecting?
|
32
|
+
@is_reflecting
|
33
|
+
end
|
34
|
+
|
35
|
+
def has_finished_reflecting?
|
36
|
+
if is_reflecting?
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
if has_empty_reflections?
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
return true
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/lib/Reflection.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
class Reflection
|
2
|
+
|
3
|
+
# Keys.
|
4
|
+
TIME = "t"
|
5
|
+
INPUT = "i"
|
6
|
+
OUTPUT = "o"
|
7
|
+
TYPE = "T"
|
8
|
+
COUNT = "C"
|
9
|
+
VALUE = "V"
|
10
|
+
STATUS = "s"
|
11
|
+
MESSAGE = "m"
|
12
|
+
# Values.
|
13
|
+
PASS = "p"
|
14
|
+
FAIL = "f"
|
15
|
+
|
16
|
+
attr_accessor :clone
|
17
|
+
|
18
|
+
def initialize(execution, method, is_control)
|
19
|
+
|
20
|
+
@execution = execution
|
21
|
+
@method = method
|
22
|
+
@is_control = is_control
|
23
|
+
|
24
|
+
# Clone the execution's object.
|
25
|
+
@clone = execution.object.clone
|
26
|
+
@clone_id = nil
|
27
|
+
|
28
|
+
# Result.
|
29
|
+
@status = nil
|
30
|
+
@time = Time.now.to_i
|
31
|
+
@input = []
|
32
|
+
@output = nil
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Reflect on a method.
|
38
|
+
#
|
39
|
+
# Creates a shadow execution stack.
|
40
|
+
#
|
41
|
+
# @param method - The name of the method.
|
42
|
+
# @param *args - The method arguments.
|
43
|
+
#
|
44
|
+
# @return - A reflection hash.
|
45
|
+
##
|
46
|
+
def reflect(*args)
|
47
|
+
|
48
|
+
# Reflect on real world arguments.
|
49
|
+
if @is_control
|
50
|
+
@input = *args
|
51
|
+
# Reflect on deviated arguments.
|
52
|
+
else
|
53
|
+
args.each do |arg|
|
54
|
+
case arg
|
55
|
+
when Integer
|
56
|
+
@input << rand(9999)
|
57
|
+
else
|
58
|
+
@input << arg
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Action method with new arguments.
|
64
|
+
begin
|
65
|
+
@output = @clone.send(@method, *@input)
|
66
|
+
# When fail.
|
67
|
+
rescue StandardError => message
|
68
|
+
@status = MESSAGE
|
69
|
+
@message = message
|
70
|
+
# When pass.
|
71
|
+
else
|
72
|
+
@status = PASS
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
def result()
|
78
|
+
# Build reflection.
|
79
|
+
reflection = {
|
80
|
+
TIME => @time,
|
81
|
+
STATUS => @status,
|
82
|
+
INPUT => normalize_input(@input),
|
83
|
+
OUTPUT => normalize_output(@output),
|
84
|
+
MESSAGE => @message
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Normalize inputs.
|
90
|
+
#
|
91
|
+
# @param args - The actual inputs.
|
92
|
+
# @return - A generic inputs representation.
|
93
|
+
##
|
94
|
+
def normalize_input(args)
|
95
|
+
inputs = []
|
96
|
+
args.each do |arg|
|
97
|
+
input = {
|
98
|
+
TYPE => arg.class.to_s,
|
99
|
+
VALUE => normalize_value(arg)
|
100
|
+
}
|
101
|
+
if (arg.class == Array)
|
102
|
+
input[COUNT] = arg.count
|
103
|
+
end
|
104
|
+
inputs << input
|
105
|
+
end
|
106
|
+
inputs
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Normalize output.
|
111
|
+
#
|
112
|
+
# @param input - The actual output.
|
113
|
+
# @return - A generic output representation.
|
114
|
+
##
|
115
|
+
def normalize_output(input)
|
116
|
+
|
117
|
+
output = {
|
118
|
+
TYPE => input.class.to_s,
|
119
|
+
VALUE => normalize_value(input)
|
120
|
+
}
|
121
|
+
|
122
|
+
if (input.class == Array || input.class == Hash)
|
123
|
+
output[COUNT] = input.count
|
124
|
+
elsif (input.class == TrueClass || input.class == FalseClass)
|
125
|
+
output[TYPE] = :Boolean
|
126
|
+
end
|
127
|
+
|
128
|
+
return output
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
def normalize_value(value)
|
133
|
+
|
134
|
+
unless value.nil?
|
135
|
+
value = value.to_s.gsub(/\r?\n/, " ").to_s
|
136
|
+
if value.length >= 30
|
137
|
+
value = value[0, value.rindex(/\s/,30)].rstrip() + '...'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
return value
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
data/lib/ShadowStack.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
################################################################################
|
2
|
+
# SHADOW STACK
|
3
|
+
#
|
4
|
+
# Track the executions in a call stack.
|
5
|
+
################################################################################
|
6
|
+
|
7
|
+
class ShadowStack
|
8
|
+
|
9
|
+
def initialize()
|
10
|
+
@bottom = nil
|
11
|
+
@top = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def peek()
|
15
|
+
@top
|
16
|
+
end
|
17
|
+
|
18
|
+
def base()
|
19
|
+
@bottom
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Push Execution.
|
24
|
+
#
|
25
|
+
# @param object - The object being executed.
|
26
|
+
# @param args - The arguments being executed.
|
27
|
+
#
|
28
|
+
# @return Execution - The new execution.
|
29
|
+
##
|
30
|
+
def push(execution)
|
31
|
+
|
32
|
+
# Reference previous execution.
|
33
|
+
if @bottom.nil?
|
34
|
+
@bottom = execution
|
35
|
+
else
|
36
|
+
execution.child = @top
|
37
|
+
@top.parent = execution
|
38
|
+
end
|
39
|
+
|
40
|
+
# Place new execution at the top of the stack.
|
41
|
+
@top = execution
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
def display
|
46
|
+
display_execution_tree(@bottom)
|
47
|
+
end
|
48
|
+
|
49
|
+
def display_execution_tree(execution)
|
50
|
+
p execution
|
51
|
+
unless execution.parent == nil
|
52
|
+
display_execution_tree(execution.parent)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/lib/reflekt.rb
CHANGED
@@ -1,11 +1,19 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'erb'
|
3
3
|
require 'rowdb'
|
4
|
+
require 'Execution'
|
5
|
+
require 'Reflection'
|
6
|
+
require 'ShadowStack'
|
4
7
|
|
5
8
|
################################################################################
|
6
9
|
# REFLEKT
|
7
10
|
#
|
8
|
-
#
|
11
|
+
# An Execution is created each time a method is called.
|
12
|
+
# Multiple Refections are created per Execution.
|
13
|
+
# These Reflections execute on a ShadowStack on cloned objects.
|
14
|
+
# Then flow is returned to the original method and normal execution continues.
|
15
|
+
#
|
16
|
+
# Usage:
|
9
17
|
#
|
10
18
|
# class ExampleClass
|
11
19
|
# prepend Reflekt
|
@@ -13,186 +21,105 @@ require 'rowdb'
|
|
13
21
|
|
14
22
|
module Reflekt
|
15
23
|
|
16
|
-
#
|
17
|
-
|
18
|
-
REFLEKT_INPUT = "i"
|
19
|
-
REFLEKT_OUTPUT = "o"
|
20
|
-
REFLEKT_TYPE = "T"
|
21
|
-
REFLEKT_COUNT = "C"
|
22
|
-
REFLEKT_VALUE = "V"
|
23
|
-
REFLEKT_STATUS = "s"
|
24
|
-
REFLEKT_MESSAGE = "m"
|
25
|
-
# Reflection values.
|
26
|
-
REFLEKT_PASS = "p"
|
27
|
-
REFLEKT_FAIL = "f"
|
28
|
-
|
29
|
-
@@reflekt_clone_count = 5
|
24
|
+
# The amount of reflections to create per method call.
|
25
|
+
@@reflekt_reflect_amount = 2
|
30
26
|
|
31
|
-
|
27
|
+
# Limit the amount of reflections that can be created per instance method.
|
28
|
+
# A method called thousands of times doesn't need that many reflections.
|
29
|
+
@@reflection_limit = 10
|
32
30
|
|
33
|
-
|
34
|
-
@reflekt_clones = []
|
31
|
+
def initialize(*args)
|
35
32
|
|
36
|
-
|
37
|
-
# A method called thousands of times doesn't need that many reflections.
|
38
|
-
@reflekt_limit = 5
|
39
|
-
@reflekt_count = 0
|
33
|
+
@reflection_counts = {}
|
40
34
|
|
41
|
-
#
|
35
|
+
# Get instance methods.
|
36
|
+
# TODO: Include parent methods like "Array.include?".
|
42
37
|
self.class.instance_methods(false).each do |method|
|
43
|
-
self.define_singleton_method(method) do |*args|
|
44
38
|
|
45
|
-
|
46
|
-
|
39
|
+
# Don't process skipped methods.
|
40
|
+
next if self.class.reflekt_skipped?(method)
|
47
41
|
|
48
|
-
|
49
|
-
unless self.class.deflekted?(method)
|
42
|
+
@reflection_counts[method] = 0
|
50
43
|
|
51
|
-
|
52
|
-
|
53
|
-
reflekt_action(clone, method, *args)
|
54
|
-
end
|
44
|
+
# When method called in flow.
|
45
|
+
self.define_singleton_method(method) do |*args|
|
55
46
|
|
56
|
-
|
57
|
-
|
47
|
+
# Don't reflect when limit reached.
|
48
|
+
unless @reflection_counts[method] >= @@reflection_limit
|
58
49
|
|
59
|
-
|
50
|
+
# Get current execution.
|
51
|
+
execution = @@reflekt_stack.peek()
|
60
52
|
|
61
|
-
|
62
|
-
|
63
|
-
end
|
53
|
+
# When stack empty or past execution done reflecting.
|
54
|
+
if execution.nil? || execution.has_finished_reflecting?
|
64
55
|
|
65
|
-
|
56
|
+
# Create execution.
|
57
|
+
execution = Execution.new(self, @@reflekt_reflect_amount)
|
58
|
+
@@reflekt_stack.push(execution)
|
66
59
|
|
67
|
-
|
68
|
-
super *args
|
69
|
-
end
|
60
|
+
end
|
70
61
|
|
71
|
-
|
62
|
+
# Reflect.
|
63
|
+
# The first method call in the Execution creates a Reflection.
|
64
|
+
# Subsequent method calls are shadow executions on cloned objects.
|
65
|
+
if execution.has_empty_reflections? && !execution.is_reflecting?
|
66
|
+
execution.is_reflecting = true
|
72
67
|
|
73
|
-
|
74
|
-
|
68
|
+
# Multiple reflections per execution.
|
69
|
+
execution.reflections.each_with_index do |value, index|
|
75
70
|
|
76
|
-
|
77
|
-
|
71
|
+
# Flag first reflection is a control.
|
72
|
+
is_control = false
|
73
|
+
is_control = true if index == 0
|
78
74
|
|
79
|
-
|
75
|
+
# Create reflection.
|
76
|
+
reflection = Reflection.new(execution, method, is_control)
|
77
|
+
execution.reflections[index] = reflection
|
80
78
|
|
81
|
-
|
79
|
+
# Execute reflection.
|
80
|
+
reflection.reflect(*args)
|
82
81
|
|
83
|
-
|
84
|
-
|
85
|
-
|
82
|
+
# Add result.
|
83
|
+
class_name = execution.caller_class.to_s
|
84
|
+
method_name = method.to_s
|
85
|
+
@@reflekt_db.get("#{class_name}.#{method_name}").push(reflection.result())
|
86
86
|
|
87
|
-
|
87
|
+
end
|
88
88
|
|
89
|
-
|
89
|
+
# Save results.
|
90
|
+
@@reflekt_db.write()
|
91
|
+
|
92
|
+
# Render results.
|
93
|
+
reflekt_render()
|
90
94
|
|
91
|
-
|
95
|
+
execution.is_reflecting = false
|
96
|
+
end
|
92
97
|
|
93
|
-
|
94
|
-
method_name = method.to_s
|
98
|
+
end
|
95
99
|
|
96
|
-
|
100
|
+
@reflection_counts[method] = @reflection_counts[method] + 1
|
97
101
|
|
98
|
-
|
99
|
-
|
102
|
+
# Continue execution / shadow execution.
|
103
|
+
super *args
|
100
104
|
|
101
|
-
args.each do |arg|
|
102
|
-
case arg
|
103
|
-
when Integer
|
104
|
-
input << rand(9999)
|
105
|
-
else
|
106
|
-
input << arg
|
107
105
|
end
|
108
|
-
end
|
109
106
|
|
110
|
-
# Action method with new arguments.
|
111
|
-
begin
|
112
|
-
output = clone.send(method, *input)
|
113
|
-
|
114
|
-
# Build reflection.
|
115
|
-
reflection = {
|
116
|
-
REFLEKT_TIME => Time.now.to_i,
|
117
|
-
REFLEKT_INPUT => reflekt_normalize_input(input),
|
118
|
-
REFLEKT_OUTPUT => reflekt_normalize_output(output)
|
119
|
-
}
|
120
|
-
|
121
|
-
# When fail.
|
122
|
-
rescue StandardError => message
|
123
|
-
reflection[REFLEKT_STATUS] = REFLEKT_MESSAGE
|
124
|
-
reflection[REFLEKT_MESSAGE] = message
|
125
|
-
# When pass.
|
126
|
-
else
|
127
|
-
reflection[REFLEKT_STATUS] = REFLEKT_PASS
|
128
107
|
end
|
129
108
|
|
130
|
-
#
|
131
|
-
|
132
|
-
|
133
|
-
end
|
109
|
+
# Continue initialization.
|
110
|
+
super
|
134
111
|
|
135
|
-
##
|
136
|
-
# Normalize inputs.
|
137
|
-
#
|
138
|
-
# @param args - The actual inputs.
|
139
|
-
# @return - A generic inputs representation.
|
140
|
-
##
|
141
|
-
def reflekt_normalize_input(args)
|
142
|
-
inputs = []
|
143
|
-
args.each do |arg|
|
144
|
-
input = {
|
145
|
-
REFLEKT_TYPE => arg.class.to_s,
|
146
|
-
REFLEKT_VALUE => reflekt_normalize_value(arg)
|
147
|
-
}
|
148
|
-
if (arg.class == Array)
|
149
|
-
input[REFLEKT_COUNT] = arg.count
|
150
|
-
end
|
151
|
-
inputs << input
|
152
|
-
end
|
153
|
-
inputs
|
154
112
|
end
|
155
113
|
|
156
114
|
##
|
157
|
-
#
|
158
|
-
#
|
159
|
-
# @param output - The actual output.
|
160
|
-
# @return - A generic output representation.
|
115
|
+
# Render results.
|
161
116
|
##
|
162
|
-
def reflekt_normalize_output(output)
|
163
|
-
|
164
|
-
o = {
|
165
|
-
REFLEKT_TYPE => output.class.to_s,
|
166
|
-
REFLEKT_VALUE => reflekt_normalize_value(output)
|
167
|
-
}
|
168
|
-
|
169
|
-
if (output.class == Array || output.class == Hash)
|
170
|
-
o[REFLEKT_COUNT] = output.count
|
171
|
-
elsif (output.class == TrueClass || output.class == FalseClass)
|
172
|
-
o[REFLEKT_TYPE] = :Boolean
|
173
|
-
end
|
174
|
-
|
175
|
-
return o
|
176
|
-
|
177
|
-
end
|
178
|
-
|
179
|
-
def reflekt_normalize_value(value)
|
180
|
-
|
181
|
-
unless value.nil?
|
182
|
-
value = value.to_s.gsub(/\r?\n/, " ").to_s
|
183
|
-
if value.length >= 30
|
184
|
-
value = value[0, value.rindex(/\s/,30)].rstrip() + '...'
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
return value
|
189
|
-
|
190
|
-
end
|
191
|
-
|
192
117
|
def reflekt_render()
|
193
118
|
|
194
|
-
#
|
119
|
+
# Get JSON.
|
195
120
|
@@reflekt_json = File.read("#{@@reflekt_output_path}/db.json")
|
121
|
+
|
122
|
+
# Save HTML.
|
196
123
|
template = File.read("#{@@reflekt_path}/web/template.html.erb")
|
197
124
|
rendered = ERB.new(template).result(binding)
|
198
125
|
File.open("#{@@reflekt_output_path}/index.html", 'w+') do |f|
|
@@ -225,20 +152,24 @@ module Reflekt
|
|
225
152
|
# Setup class.
|
226
153
|
def self.reflekt_setup_class()
|
227
154
|
|
228
|
-
# Receive configuration
|
155
|
+
# Receive configuration.
|
229
156
|
$ENV ||= {}
|
230
157
|
$ENV[:reflekt] ||= $ENV[:reflekt] = {}
|
231
158
|
|
159
|
+
# Set configuration.
|
232
160
|
@@reflekt_path = File.dirname(File.realpath(__FILE__))
|
233
161
|
|
234
|
-
# Create
|
162
|
+
# Create reflection tree.
|
163
|
+
@@reflekt_stack = ShadowStack.new()
|
164
|
+
|
165
|
+
# Build reflections directory.
|
235
166
|
if $ENV[:reflekt][:output_path]
|
236
167
|
@@reflekt_output_path = File.join($ENV[:reflekt][:output_path], 'reflections')
|
237
|
-
#
|
168
|
+
# Build reflections directory in current execution path.
|
238
169
|
else
|
239
170
|
@@reflekt_output_path = File.join(Dir.pwd, 'reflections')
|
240
171
|
end
|
241
|
-
|
172
|
+
# Create reflections directory.
|
242
173
|
unless Dir.exist? @@reflekt_output_path
|
243
174
|
Dir.mkdir(@@reflekt_output_path)
|
244
175
|
end
|
@@ -252,7 +183,7 @@ module Reflekt
|
|
252
183
|
|
253
184
|
module SingletonClassMethods
|
254
185
|
|
255
|
-
@@
|
186
|
+
@@reflekt_skipped_methods = Set.new
|
256
187
|
|
257
188
|
##
|
258
189
|
# Skip a method.
|
@@ -260,16 +191,16 @@ module Reflekt
|
|
260
191
|
# @param method - A symbol representing the method name.
|
261
192
|
##
|
262
193
|
def reflekt_skip(method)
|
263
|
-
@@
|
194
|
+
@@reflekt_skipped_methods.add(method)
|
264
195
|
end
|
265
196
|
|
266
|
-
def
|
267
|
-
return true if @@
|
197
|
+
def reflekt_skipped?(method)
|
198
|
+
return true if @@reflekt_skipped_methods.include?(method)
|
268
199
|
false
|
269
200
|
end
|
270
201
|
|
271
202
|
def reflekt_limit(amount)
|
272
|
-
|
203
|
+
@@reflection_limit = amount
|
273
204
|
end
|
274
205
|
|
275
206
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reflekt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maedi Prichard
|
@@ -30,11 +30,14 @@ executables: []
|
|
30
30
|
extensions: []
|
31
31
|
extra_rdoc_files: []
|
32
32
|
files:
|
33
|
+
- lib/Execution.rb
|
34
|
+
- lib/Reflection.rb
|
35
|
+
- lib/ShadowStack.rb
|
33
36
|
- lib/reflekt.rb
|
34
37
|
- lib/web/script.js
|
35
38
|
- lib/web/style.css
|
36
39
|
- lib/web/template.html.erb
|
37
|
-
homepage: https://github.com/
|
40
|
+
homepage: https://github.com/refIekt/reflekt
|
38
41
|
licenses:
|
39
42
|
- MPL-2.0
|
40
43
|
metadata: {}
|