-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathstatic_analyzer.rb
314 lines (227 loc) · 4.24 KB
/
static_analyzer.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
require 'rubygems'
require 'parse_tree'
class Node
attr_reader :children
attr_reader :parent
attr_reader :file
def initialize(file, children)
@file = file
@children = children
children.each do |child|
child.parent = self if child.is_a? Node
end
end
def parent=(parent)
raise "Parent already assigned" if @parent
@parent = parent
end
def to_s
"(#{([self.class] + children).join(' ')})"
end
def self.to_s
# Class name always begins with "Node_", so drop that part.
name[5..-1]
end
end
class Node_op_asgn2 < Node
end
class Node_until < Node
end
class Node_not < Node
end
class Node_evstr < Node
end
class Node_ivar < Node
end
class Node_call < Node
end
class Node_dot2 < Node
end
class Node_svalue < Node
end
class Node_match3 < Node
end
class Node_rescue < Node
end
class Node_iasgn < Node
end
class Node_if < Node
end
class Node_arglist < Node
end
class Node_op_asgn_and < Node
end
class Node_dregx < Node
end
class Node_attrasgn < Node
end
class Node_masgn < Node
end
class Node_gvar < Node
end
class Node_iter < Node
end
class Node_class < Node
end
class Node_defn < Node
end
class Node_next < Node
end
class Node_cdecl < Node
end
class Node_case < Node
end
class Node_resbody < Node
end
class Node_lit < Node
end
class Node_cvar < Node
end
class Node_to_ary < Node
end
class Node_for < Node
end
class Node_sclass < Node
end
class Node_lasgn < Node
end
class Node_op_asgn_or < Node
end
class Node_cvasgn < Node
end
class Node_block_pass < Node
end
class Node_when < Node
end
class Node_dstr < Node
end
class Node_return < Node
end
class Node_ensure < Node
end
class Node_break < Node
end
class Node_defined < Node
end
class Node_nth_ref < Node
end
class Node_args < Node
end
class Node_colon2 < Node
end
class Node_cvdecl < Node
end
class Node_xstr < Node
end
class Node_defs < Node
end
class Node_and < Node
end
class Node_yield < Node
end
class Node_hash < Node
end
class Node_const < Node
end
class Node_colon3 < Node
end
class Node_alias < Node
end
class Node_op_asgn1 < Node
end
class Node_dxstr < Node
end
class Node_while < Node
end
class Node_or < Node
end
class Node_array < Node
end
class Node_lvar < Node
end
class Node_module < Node
end
class Node_str < Node
end
class Node_match2 < Node
end
class Node_super < Node
end
class Node_gasgn < Node
end
class Node_splat < Node
end
class Node_scope < Node
end
class Node_block < Node
end
class Node_true < Node
end
class Node_false < Node
end
class Node_nil < Node
end
class Node_zsuper < Node
end
class Node_self < Node
end
class Node_retry < Node
end
class Node_dot3 < Node
end
class Node_redo < Node
end
NODE_CLASSES = Hash.new { |hash, key| hash[key] = Kernel.const_get("Node_#{key}") }
class Analyzer
def initialize
@node_types = Hash.new { |hash, key| hash[key] = [] }
end
attr_reader :node_types
def check_patterns(node)
if node.class == Node_array and node.children.length > 25
puts "Found one: #{node} in file #{node.file}"
end
end
def traverse(node)
check_patterns node
type = node.class
node.children.each_with_index do |x, i|
@node_types[type][i] ||= Hash.new { |hash, key| hash[key] = 0 }
if x.is_a? Node
@node_types[type][i][x.class] += 1
traverse x
else
@node_types[type][i][x.class] += 1
end
end
end
def convert_to_nodes(file, sexp)
if sexp.is_a? Sexp
cls = NODE_CLASSES[sexp.sexp_type]
children = sexp.sexp_body.map { |child| convert_to_nodes(file, child) }
cls.new(file, children)
else
sexp
end
end
def investigate_path(path)
if File.directory?(path)
return if File.basename(path) == '.git'
Dir.foreach(path) do |x|
next if ['.', '..'].include? x
investigate_path File.join(path, x)
end
else
return if File.extname(path) != '.rb' # TODO: handle shebang files
contents = File.read path
parser = ParseTree.new(false) # true = include newlines
sexp = parser.process(contents, nil, path)
puts "Analyzing #{path}"
traverse(convert_to_nodes(path, sexp)) if sexp # empty files yield nil, heh.
end
end
end
analyzer = Analyzer.new
analyzer.investigate_path(ARGV[0])
require 'pp'
pp analyzer.node_types