flaw_detector 0.0.1

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.
@@ -0,0 +1,190 @@
1
+ module FlawDetector
2
+ module CodeModel
3
+ class CfgNode
4
+ def create_sibling(bb_name)
5
+ cfg = CfgNode.new(bb_name, @insns_block)
6
+ add_list(cfg)
7
+ end
8
+
9
+ def initialize(bb_name, insns_block)
10
+ @next_nodes = []
11
+ @prev_nodes = []
12
+ @name = bb_name
13
+ @insns_block = insns_block
14
+ @leave = nil
15
+ @dfst_parent = nil
16
+ @dominators = nil
17
+ end
18
+
19
+ def inspect
20
+ return "#{self.class.name}:#{self.object_id} @name='#{self.name}',@insns_block='#{self.insns_block}'>"
21
+ end
22
+
23
+ def add_next(node)
24
+ @next_nodes << node
25
+ end
26
+
27
+ def add_prev(node)
28
+ @prev_nodes << node
29
+ end
30
+
31
+ def set_leave(node)
32
+ if entry == self
33
+ @leave = node
34
+ else
35
+ entry.set_leave(node)
36
+ end
37
+ end
38
+
39
+ def leave
40
+ if entry == self
41
+ @leave
42
+ else
43
+ entry.leave
44
+ end
45
+ end
46
+
47
+ # @todo exclude...
48
+ def last_line
49
+ insns_frame.index2line(bb[0].last-1)
50
+ end
51
+
52
+ def each(&block)
53
+ each_node_list do |name, node|
54
+ block.call(name, node)
55
+ node.next_nodes.each do |nnd|
56
+ next unless nnd.name == :entry
57
+ nnd.each_node_list(&block)
58
+ end
59
+ end
60
+ end
61
+
62
+ def each_dfst_node_by_preorder(&block)
63
+ each_dfst_node(:preorder, &block)
64
+ end
65
+
66
+ def each_dfst_node_by_postorder(&block)
67
+ each_dfst_node(:postorder, &block)
68
+ end
69
+
70
+ def each_dfst_node_by_reverse_postorder(&block)
71
+ nodes = []
72
+ each_dfst_node_by_postorder do |node|
73
+ nodes << node
74
+ end
75
+
76
+ nodes.reverse_each(&block)
77
+ end
78
+
79
+ def each_node_list(&block)
80
+ node_list.each(&block)
81
+ end
82
+
83
+ def entry
84
+ node_list[:entry]
85
+ end
86
+
87
+ def node(bb_name)
88
+ node_list[bb_name]
89
+ end
90
+
91
+ def total_node_count
92
+ self.node_list.count
93
+ end
94
+
95
+ def bb
96
+ @insns_block.raw_block_data[:extra][:basic_blocks][name]
97
+ end
98
+
99
+ def last_lineno
100
+ @insns_block.index2line(bb[0].last+1)
101
+ end
102
+
103
+ # @todo support ISeqHeader::TYPE_BLOCK
104
+ def dominate?(node)
105
+ return false unless @insns_block == node.insns_block
106
+ return false if insns_frame.type == ISeqHeader::TYPE_BLOCK
107
+ return true if self.name == node.name
108
+ idom = node
109
+ while idom = @insns_block.domtree[idom.name] do
110
+ return true if self.name == idom.name
111
+ end
112
+ return false
113
+ end
114
+
115
+ def is_dominance_frontier_of?(node)
116
+ if node.prev_nodes.first == node.prev_nodes.last
117
+ pfpl = plpf = []
118
+ else
119
+ pfpl = node.prev_nodes.first.dominators - node.prev_nodes.last.dominators
120
+ plpf = node.prev_nodes.last.dominators - node.prev_nodes.first.dominators
121
+ end
122
+ pfpl.include?(self) || plpf.include?(self)
123
+ end
124
+
125
+ def dominators
126
+ return @dominators if @dominators
127
+ @dominators = []
128
+ each_node_list do |name, node|
129
+ @dominators << node if node.dominate?(self)
130
+ end
131
+ return @dominators
132
+ end
133
+
134
+ def visit_specified_dominators(&block)
135
+ checked_nodes = []
136
+ stack_of_nodes = [self]
137
+ until stack_of_nodes.empty? do
138
+ node = stack_of_nodes.pop
139
+ next unless self.dominate?(node)
140
+ next if checked_nodes.include?(node)
141
+ checked_nodes << node
142
+ n = block.call(node)
143
+ case n
144
+ when :first, :last
145
+ next_nodes = [node.next_nodes.send(n)]
146
+ when :all
147
+ next_nodes = node.next_nodes
148
+ when :none
149
+ next_nodes = []
150
+ else
151
+ raise "unknown state"
152
+ end
153
+ next_nodes.each do |nnd|
154
+ next if nnd.name == :entry
155
+ stack_of_nodes << nnd
156
+ end
157
+ end
158
+ end
159
+
160
+ attr_reader :next_nodes,:prev_nodes,:name,:leave,:insns_block,:dfst_parent
161
+ alias :insns_frame :insns_block
162
+
163
+ protected
164
+ attr_writer :dfst_parent
165
+
166
+ def add_list(node)
167
+ node_list[node.name] = node
168
+ end
169
+
170
+ def node_list
171
+ @insns_block.cfg_node_list.keys
172
+ @insns_block.cfg_node_list
173
+ end
174
+
175
+ def each_dfst_node(type = :preorder, position = :entry, found = [], &block)
176
+ node = node_list[position]
177
+ found << node
178
+
179
+ block.call(node) if type == :preorder
180
+ node.next_nodes.each do |nnd|
181
+ next if nnd.name == :entry #skip another frame
182
+ next if found.include?(nnd)
183
+ nnd.dfst_parent = node
184
+ each_dfst_node(type, nnd.name, found, &block)
185
+ end
186
+ block.call(node) if type == :postorder
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,63 @@
1
+ module FlawDetector
2
+ module CodeModel
3
+ class CodeDocument
4
+ def self.create(doc, filepath = "<compiled>")
5
+ return CodeDocument.new(doc, filepath)
6
+ end
7
+
8
+ def top
9
+ root
10
+ end
11
+
12
+ def select_methods(name=nil)
13
+ select_some_type(ISeqHeader::TYPE_METHOD, name)
14
+ end
15
+
16
+ def select_classes(name=nil)
17
+ class_name = nil
18
+ class_name = "<class:#{name}>" if name
19
+ select_some_type(ISeqHeader::TYPE_CLASS, class_name)
20
+ end
21
+
22
+ def select_class(name)
23
+ root.each do |block|
24
+ next unless block.type == ISeqHeader::TYPE_CLASS
25
+ next unless block.name == "<class:#{name}>"
26
+ return block
27
+ end
28
+ return nil
29
+ end
30
+
31
+ attr_reader :root
32
+ attr_reader :filepath
33
+ private
34
+ def initialize(str, filepath)
35
+ @root = InsnsFrame.new(str)
36
+ @filepath = filepath
37
+ end
38
+
39
+ def select_some_type(type, name=nil)
40
+ selects = []
41
+ b = proc do |parent|
42
+ parent.child_blocks.each do |frame|
43
+ unless frame.type == type
44
+ b.call(frame)
45
+ next
46
+ end
47
+ if name.nil? || frame.name == name
48
+ selects << frame
49
+ end
50
+ end
51
+ end
52
+
53
+ if root.type == type
54
+ if name.nil? || root.name == name
55
+ selects << frame
56
+ end
57
+ end
58
+ b.call(root)
59
+ selects
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,341 @@
1
+ module FlawDetector
2
+ module CodeModel
3
+ class InsnsFrame
4
+ def initialize(iseq_data,pblock=nil, insns_pos=nil)
5
+ @raw_block_data = iseq_data
6
+ @child_blocks = []
7
+ @parent = pblock
8
+ @insns_pos = insns_pos
9
+ @cfg_node_list = {}
10
+ @domtree = nil
11
+
12
+ if @raw_block_data
13
+ @raw_block_data[:extra][:insns_pos_to_operand_iseq].each do |insns_pos, raw_block|
14
+ if raw_block
15
+ @child_blocks << InsnsFrame.new(raw_block, self, insns_pos)
16
+ else
17
+ @child_blocks << InsnsFrame.new(nil, self, insns_pos)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def inspect
24
+ return "#{self.class.name}:#{self.object_id} @name='#{self.name}',@type='#{self.type}',@insns_pos='#{@insns_pos}',@child_frames=#{@child_blocks}>"
25
+ end
26
+
27
+ def insns_pos_in_parent_body
28
+ @insns_pos
29
+ end
30
+
31
+ def name
32
+ if @raw_block_data
33
+ @raw_block_data[:header][:name].to_s
34
+ else
35
+ ""
36
+ end
37
+ end
38
+
39
+ def type
40
+ if @raw_block_data
41
+ @raw_block_data[:header][:type]
42
+ else
43
+ nil
44
+ end
45
+ end
46
+
47
+ def insns
48
+ if @raw_block_data
49
+ s = @raw_block_data[:body][:insns].clone
50
+ else
51
+ s = []
52
+ end
53
+ s.freeze
54
+ end
55
+
56
+ def each(&block)
57
+ block.call(self)
58
+ @child_blocks.each(&block)
59
+ end
60
+
61
+ def basic_blocks
62
+ raw_block_data[:extra][:basic_blocks].dup.freeze
63
+ end
64
+
65
+ # == Arguments & Return
66
+ # _varname_ :: [Symbol] variable name
67
+ # *Return* :: [Integer] idx which is operand for getlocal, setlocal and so on
68
+ def varname2idx(varsym)
69
+ cnt = raw_block_data[:header][:locals].count
70
+ index = raw_block_data[:header][:locals].find_index(varsym)
71
+ if index
72
+ return cnt - index + 1
73
+ else
74
+ return nil
75
+ end
76
+ end
77
+
78
+ # == Arguments & Return
79
+ # _idx_ :: [Integer] idx which is operand for getlocal, setlocal and so on
80
+ # *Return* :: [Symbol] variable name
81
+ def idx2varname(idx)
82
+ cnt = raw_block_data[:header][:locals].count
83
+ raw_block_data[:header][:locals][cnt+1-idx]
84
+ end
85
+
86
+ def insn2variable(insn)
87
+ frame = nil
88
+ varname = nil
89
+ case insn[0]
90
+ when :getlocal,:setlocal
91
+ idx = insn[1]
92
+ frame = self
93
+ varname = frame.idx2varname(idx)
94
+ when :getdynamic,:setdynamic
95
+ idx = insn[1]
96
+ up_frame = insn[2]
97
+ frame = self
98
+ insn[2].times{frame = frame.parent}
99
+ varname = frame.idx2varname(idx)
100
+ else
101
+ raise 'unknown state'
102
+ end
103
+ return frame, varname
104
+ end
105
+
106
+ def entry_cfg_node
107
+ unless @cfg_node_list[:entry]
108
+ top_of_block = self
109
+ while top_of_block.raw_block_data[:header][:type] == ISeqHeader::TYPE_BLOCK do
110
+ top_of_block = top_of_block.parent
111
+ unless top_of_block
112
+ raise "unknown state"
113
+ end
114
+ end
115
+ top_of_block.make_cfg
116
+ unless @cfg_node_list[:entry]
117
+ #ASSERT: make_cfg method creates cfg nodes of top/method and child blocks
118
+ raise "unknown state"
119
+ end
120
+ end
121
+ @cfg_node_list[:entry]
122
+ end
123
+
124
+ def find_cfg_node_by_insn_index(index)
125
+ entry_cfg_node
126
+ @cfg_node_list.each do |key, val|
127
+ return val if val.bb[0].include?(index)
128
+ end
129
+ end
130
+
131
+ def find_insn_index_of_send_obj(index, insn)
132
+ find_def_insn_index_of_arg(index, insn)
133
+ end
134
+
135
+ def find_use_insn_index_of_ret(index, insn, retnum=0)
136
+ if retnum >= InsnExt::insn_ret_num(InsnExt::insn_num(insn[0]))
137
+ raise "wrong argument: retnum=#{retnum}"
138
+ end
139
+
140
+ depth = retnum
141
+ obj_index = nil
142
+ start_index = index+1
143
+ cfg_node = find_cfg_node_by_insn_index(index)
144
+ unless cfg_node.bb[0].include?(start_index)
145
+ if cfg_node.next_nodes.count == 1 #TODO: exclude different frame
146
+ cfg_node = cfg_node.next_nodes.first
147
+ start_index = cfg_node.bb[0].first
148
+ else
149
+ cfg_node = nil
150
+ end
151
+ end
152
+ while cfg_node do
153
+ (start_index...(cfg_node.bb[0].last)).each do |tmp_index|
154
+ tmp_insn = self.raw_block_data[:body][:insns][tmp_index]
155
+ tmp_ret = InsnExt::insn_ret_num(InsnExt::insn_num(tmp_insn[0]))
156
+ tmp_inc = InsnExt::insn_stack_increase(InsnExt::insn_num(tmp_insn[0]), tmp_insn[1..-1])
157
+ depth += tmp_inc - tmp_ret
158
+ if depth < 0
159
+ obj_index = tmp_index
160
+ break
161
+ end
162
+ depth += tmp_ret
163
+ end
164
+ break if obj_index
165
+ if cfg_node.next_nodes.count == 1 #TODO: exclude different frame
166
+ cfg_node = cfg_node.next_nodes.first
167
+ start_index = cfg_node.bb[0].first
168
+ else
169
+ cfg_node = nil
170
+ end
171
+ end
172
+
173
+ obj_argnum = nil
174
+ if obj_index
175
+ obj_argnum = -(depth + 1)
176
+ end
177
+ return obj_index, obj_argnum
178
+ end
179
+
180
+ def find_def_insn_index_of_arg(index, insn, argnum=0)
181
+ inc = InsnExt::insn_stack_increase(InsnExt::insn_num(insn[0]), insn[1..-1])
182
+ ret = InsnExt::insn_ret_num(InsnExt::insn_num(insn[0]))
183
+ depth = (inc - ret) + argnum
184
+ obj_index = nil
185
+ end_index = index
186
+ cfg_node = find_cfg_node_by_insn_index(index)
187
+ unless cfg_node.bb[0].include?(index-1)
188
+ if cfg_node.prev_nodes.count == 1 #TODO: exclude different frame
189
+ cfg_node = cfg_node.prev_nodes.first
190
+ end_index = cfg_node.bb[0].last
191
+ else
192
+ cfg_node = nil
193
+ end
194
+ end
195
+ while cfg_node
196
+ ((cfg_node.bb[0].first)...end_index).reverse_each do |tmp_index|
197
+ rinsn = self.raw_block_data[:body][:insns][tmp_index]
198
+ tmp_ret = InsnExt::insn_ret_num(InsnExt::insn_num(rinsn[0]))
199
+ if (depth + tmp_ret) >= 0
200
+ obj_index = tmp_index
201
+ break
202
+ end
203
+ depth += InsnExt::insn_stack_increase(InsnExt::insn_num(rinsn[0]), rinsn[1..-1])
204
+ end
205
+ break if obj_index
206
+ if cfg_node.prev_nodes.count == 1 #TODO: exclude different frame
207
+ cfg_node = cfg_node.prev_nodes.first
208
+ end_index = cfg_node.bb[0].last
209
+ else
210
+ cfg_node = nil
211
+ end
212
+ end
213
+
214
+ obj_retnum = nil
215
+ if obj_index
216
+ tmp_insn = self.raw_block_data[:body][:insns][obj_index]
217
+ tmp_ret = InsnExt::insn_ret_num(InsnExt::insn_num(tmp_insn[0]))
218
+ obj_retnum = depth + tmp_ret
219
+ end
220
+
221
+ return obj_index, obj_retnum
222
+ end
223
+
224
+ def index2line(index)
225
+ i = self.raw_block_data[:extra][:insns_pos_to_lineno].find_index{|elem| elem[0].include?(index)}
226
+ if i
227
+ self.raw_block_data[:extra][:insns_pos_to_lineno][i][1]
228
+ else
229
+ nil
230
+ end
231
+ end
232
+
233
+ def domtree
234
+ @domtree if @domtree
235
+ e = entry_cfg_node
236
+ idom = {}
237
+ dfsn = {}
238
+ val = 0
239
+ e.each_dfst_node_by_reverse_postorder do |node|
240
+ dfsn[node.name] = val
241
+ val += 1
242
+ end
243
+
244
+ e.each_node_list do |name, node|
245
+ idom[node.name] = node.dfst_parent
246
+ end
247
+
248
+ nca = lambda do |x,y|
249
+ while (x != y) do
250
+ x_dfsn = if x then dfsn[x.name] else nil end
251
+ y_dfsn = if y then dfsn[y.name] else nil end
252
+ if x_dfsn > y_dfsn
253
+ x = idom[x.name]
254
+ elsif x_dfsn < y_dfsn
255
+ y = idom[y.name]
256
+ end
257
+ end
258
+ x
259
+ end
260
+
261
+ change_flag = true
262
+ while(change_flag) do
263
+ change_flag = false
264
+ e.each_dfst_node_by_postorder do |v|
265
+ v.prev_nodes.each do |succ|
266
+ next if succ.name == :leave
267
+ tmp_idom = nca.call(idom[v.name], succ)
268
+ if tmp_idom != idom[v.name]
269
+ change_flag = true
270
+ idom[v.name] = tmp_idom
271
+ end
272
+ end
273
+ end
274
+ end
275
+ @domtree = idom
276
+ end
277
+
278
+ attr_reader :parent, :child_blocks, :raw_block_data, :cfg_node_list
279
+
280
+ protected
281
+ def make_cfg
282
+ bb_name = :entry
283
+ pcfg = CfgNode.new(bb_name, self)
284
+ @cfg_node_list[bb_name] = pcfg
285
+ make_cfg_inter(pcfg)
286
+
287
+ node = pcfg.node(:leave)
288
+ unless node
289
+ node = pcfg.create_sibling(:leave)
290
+ end
291
+ pcfg.set_leave(node)
292
+
293
+ return pcfg
294
+ end
295
+
296
+ private
297
+ def make_cfg_inter(pcfg)
298
+ new_node = []
299
+ data = self.raw_block_data
300
+ bbs = data[:extra][:basic_blocks]
301
+ pcfg.bb[1..-1].each do |pos|
302
+ next if [:exception].include?(pos)
303
+ key, val = bbs.find{|key,val|
304
+ if pos == :leave && key == :leave
305
+ true
306
+ else
307
+ val[0].include?(pos)
308
+ end
309
+ }
310
+
311
+ unless key
312
+ raise "unknown state"
313
+ end
314
+ node = pcfg.node(key)
315
+ unless node
316
+ node = pcfg.create_sibling(key)
317
+ new_node << node
318
+ end
319
+ pcfg.add_next(node)
320
+ node.add_prev(pcfg)
321
+ end
322
+
323
+ @child_blocks.each do |b|
324
+ next unless b.raw_block_data
325
+ next unless b.raw_block_data[:header][:type] == ISeqHeader::TYPE_BLOCK
326
+ if pcfg.bb[0].include?(b.insns_pos_in_parent_body)
327
+ node = b.make_cfg
328
+ pcfg.add_next(node)
329
+ node.add_prev(pcfg)
330
+
331
+ #TODO: is this ok? (or should use :break exception?)
332
+ pcfg.next_nodes.first.add_prev(node.leave)
333
+ node.leave.add_next(pcfg.next_nodes.first)
334
+ break
335
+ end
336
+ end
337
+ new_node.each {|n| make_cfg_inter(n)}
338
+ end
339
+ end
340
+ end
341
+ end
@@ -0,0 +1,27 @@
1
+ module FlawDetector
2
+ class BasicController
3
+ def initialize(formatter = Formatter::CsvFormatter.new)
4
+ @detectors = []
5
+ @formatter = formatter
6
+ end
7
+
8
+ # @return [Boolean] true if no flaw found, otherwise false
9
+ def run(file_or_ary)
10
+ result = []
11
+ ary = [file_or_ary] if file_or_ary.is_a?(String)
12
+ ary ||= file_or_ary
13
+ ary.each do |file|
14
+ File.open(file) do |fp|
15
+ dom = FlawDetector::parse_file(fp)
16
+ @detectors.each do |analyzer|
17
+ result += analyzer.analyze(dom)
18
+ end
19
+ end
20
+ end
21
+ @formatter.render(result)
22
+ result.empty?
23
+ end
24
+
25
+ attr_accessor :detectors
26
+ end
27
+ end
@@ -0,0 +1,10 @@
1
+ module FlawDetector
2
+ module Detector
3
+ class AbstractDetector
4
+ def detect
5
+ raise "not yet implemented."
6
+ end
7
+ end
8
+ end
9
+ end
10
+