brakeman 0.0.2
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/FEATURES +16 -0
- data/README.md +112 -0
- data/WARNING_TYPES +69 -0
- data/bin/brakeman +266 -0
- data/lib/checks.rb +67 -0
- data/lib/checks/base_check.rb +338 -0
- data/lib/checks/check_cross_site_scripting.rb +216 -0
- data/lib/checks/check_default_routes.rb +29 -0
- data/lib/checks/check_evaluation.rb +29 -0
- data/lib/checks/check_execute.rb +110 -0
- data/lib/checks/check_file_access.rb +46 -0
- data/lib/checks/check_forgery_setting.rb +25 -0
- data/lib/checks/check_mass_assignment.rb +72 -0
- data/lib/checks/check_model_attributes.rb +36 -0
- data/lib/checks/check_redirect.rb +98 -0
- data/lib/checks/check_render.rb +65 -0
- data/lib/checks/check_send_file.rb +15 -0
- data/lib/checks/check_session_settings.rb +36 -0
- data/lib/checks/check_sql.rb +124 -0
- data/lib/checks/check_validation_regex.rb +60 -0
- data/lib/format/style.css +105 -0
- data/lib/processor.rb +83 -0
- data/lib/processors/alias_processor.rb +384 -0
- data/lib/processors/base_processor.rb +235 -0
- data/lib/processors/config_processor.rb +146 -0
- data/lib/processors/controller_alias_processor.rb +222 -0
- data/lib/processors/controller_processor.rb +175 -0
- data/lib/processors/erb_template_processor.rb +84 -0
- data/lib/processors/erubis_template_processor.rb +62 -0
- data/lib/processors/haml_template_processor.rb +115 -0
- data/lib/processors/lib/find_call.rb +176 -0
- data/lib/processors/lib/find_model_call.rb +39 -0
- data/lib/processors/lib/processor_helper.rb +36 -0
- data/lib/processors/lib/render_helper.rb +118 -0
- data/lib/processors/library_processor.rb +117 -0
- data/lib/processors/model_processor.rb +125 -0
- data/lib/processors/output_processor.rb +204 -0
- data/lib/processors/params_processor.rb +77 -0
- data/lib/processors/route_processor.rb +338 -0
- data/lib/processors/template_alias_processor.rb +86 -0
- data/lib/processors/template_processor.rb +55 -0
- data/lib/report.rb +628 -0
- data/lib/scanner.rb +232 -0
- data/lib/tracker.rb +144 -0
- data/lib/util.rb +141 -0
- data/lib/warning.rb +97 -0
- metadata +191 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
#Contains a couple shared methods for Processors.
|
2
|
+
module ProcessorHelper
|
3
|
+
|
4
|
+
#Sets the current module.
|
5
|
+
def process_module exp
|
6
|
+
@current_module = class_name(exp[1]).to_s
|
7
|
+
process exp[2]
|
8
|
+
@current_module = nil
|
9
|
+
exp
|
10
|
+
end
|
11
|
+
|
12
|
+
#Returns a class name as a Symbol.
|
13
|
+
def class_name exp
|
14
|
+
case exp
|
15
|
+
when Sexp
|
16
|
+
case exp.node_type
|
17
|
+
when :const
|
18
|
+
exp[1]
|
19
|
+
when :colon2
|
20
|
+
"#{class_name(exp[1])}::#{exp[2]}".to_sym
|
21
|
+
when :colon3
|
22
|
+
"::#{exp[1]}".to_sym
|
23
|
+
when :call
|
24
|
+
process exp
|
25
|
+
else
|
26
|
+
raise "Error: Cannot get class name from #{exp}"
|
27
|
+
end
|
28
|
+
when Symbol
|
29
|
+
exp
|
30
|
+
when nil
|
31
|
+
nil
|
32
|
+
else
|
33
|
+
raise "Error: Cannot get class name from #{exp}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
#Processes a call to render() in a controller or template
|
4
|
+
module RenderHelper
|
5
|
+
|
6
|
+
#Process s(:render, TYPE, OPTIONS)
|
7
|
+
def process_render exp
|
8
|
+
process_default exp
|
9
|
+
@rendered = true
|
10
|
+
case exp[1]
|
11
|
+
when :action
|
12
|
+
process_action exp[2][1], exp[3]
|
13
|
+
when :default
|
14
|
+
process_template template_name, exp[3]
|
15
|
+
when :partial
|
16
|
+
process_partial exp[2], exp[3]
|
17
|
+
when :nothing
|
18
|
+
end
|
19
|
+
exp
|
20
|
+
end
|
21
|
+
|
22
|
+
#Determines file name for partial and then processes it
|
23
|
+
def process_partial name, args
|
24
|
+
if name == "" or !(string? name or symbol? name)
|
25
|
+
return
|
26
|
+
end
|
27
|
+
|
28
|
+
names = name[1].to_s.split("/")
|
29
|
+
names[-1] = "_" + names[-1]
|
30
|
+
process_template template_name(names.join("/")), args
|
31
|
+
end
|
32
|
+
|
33
|
+
#Processes a given action
|
34
|
+
def process_action name, args
|
35
|
+
process_template template_name(name), args
|
36
|
+
end
|
37
|
+
|
38
|
+
#Processes a template, adding any instance variables
|
39
|
+
#to its environment.
|
40
|
+
def process_template name, args, called_from = nil
|
41
|
+
#Get scanned source for this template
|
42
|
+
name = name.to_s.gsub(/^\//, "")
|
43
|
+
template = @tracker.templates[name.to_sym]
|
44
|
+
unless template
|
45
|
+
warn "[Notice] No such template: #{name}" if OPTIONS[:debug]
|
46
|
+
return
|
47
|
+
end
|
48
|
+
|
49
|
+
template_env = only_ivars
|
50
|
+
|
51
|
+
#Hash the environment and the source of the template to avoid
|
52
|
+
#pointlessly processing templates, which can become prohibitively
|
53
|
+
#expensive in terms of time and memory.
|
54
|
+
digest = Digest::SHA1.new.update(template_env.instance_variable_get(:@env).to_a.sort.to_s << template[:src].to_s).to_s.to_sym
|
55
|
+
|
56
|
+
if @tracker.template_cache.include? digest
|
57
|
+
#Already processed this template with identical environment
|
58
|
+
return
|
59
|
+
else
|
60
|
+
@tracker.template_cache << digest
|
61
|
+
|
62
|
+
options = get_options args
|
63
|
+
|
64
|
+
if hash? options[:locals]
|
65
|
+
hash_iterate options[:locals] do |key, value|
|
66
|
+
template_env[Sexp.new(:call, nil, key[1], Sexp.new(:arglist))] = value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
if options[:collection]
|
71
|
+
|
72
|
+
#The collection name is the name of the partial without the leading
|
73
|
+
#underscore.
|
74
|
+
variable = template[:name].to_s.match(/[^\/_][^\/]+$/)[0].to_sym
|
75
|
+
|
76
|
+
#Unless the :as => :variable_name option is used
|
77
|
+
if options[:as]
|
78
|
+
if string? options[:as] or symbol? options[:as]
|
79
|
+
variable = options[:as][1].to_sym
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
template_env[Sexp.new(:call, nil, variable, Sexp.new(:arglist))] = Sexp.new(:call, Sexp.new(:const, Tracker::UNKNOWN_MODEL), :new, Sexp.new(:arglist))
|
84
|
+
end
|
85
|
+
|
86
|
+
#Run source through AliasProcessor with instance variables from the
|
87
|
+
#current environment.
|
88
|
+
#TODO: Add in :locals => { ... } to environment
|
89
|
+
src = TemplateAliasProcessor.new(@tracker, template).process_safely(template[:src], template_env)
|
90
|
+
|
91
|
+
#Run alias-processed src through the template processor to pull out
|
92
|
+
#information and outputs.
|
93
|
+
#This information will be stored in tracker.templates, but with a name
|
94
|
+
#specifying this particular route. The original source should remain
|
95
|
+
#pristine (so it can be processed within other environments).
|
96
|
+
@tracker.processor.process_template name, src, template[:type], called_from
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
#Override to process name, such as adding the controller name.
|
101
|
+
def template_name name
|
102
|
+
raise "RenderHelper#template_name should be overridden."
|
103
|
+
end
|
104
|
+
|
105
|
+
#Turn options Sexp into hash
|
106
|
+
def get_options args
|
107
|
+
options = {}
|
108
|
+
return options unless hash? args
|
109
|
+
|
110
|
+
hash_iterate args do |key, value|
|
111
|
+
if symbol? key
|
112
|
+
options[key[1]] = value
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
options
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'processors/base_processor'
|
2
|
+
require 'processors/alias_processor'
|
3
|
+
|
4
|
+
#Process generic library and stores it in Tracker.libs
|
5
|
+
class LibraryProcessor < BaseProcessor
|
6
|
+
|
7
|
+
def initialize tracker
|
8
|
+
super
|
9
|
+
@file_name = nil
|
10
|
+
@alias_processor = AliasProcessor.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def process_library src, file_name = nil
|
14
|
+
@file_name = file_name
|
15
|
+
process src
|
16
|
+
end
|
17
|
+
|
18
|
+
def process_class exp
|
19
|
+
name = class_name(exp[1])
|
20
|
+
|
21
|
+
if @current_class
|
22
|
+
outer_class = @current_class
|
23
|
+
name = (outer_class[:name].to_s + "::" + name.to_s).to_sym
|
24
|
+
end
|
25
|
+
|
26
|
+
if @current_module
|
27
|
+
name = (@current_module[:name].to_s + "::" + name.to_s).to_sym
|
28
|
+
end
|
29
|
+
|
30
|
+
if @tracker.libs[name]
|
31
|
+
@current_class = @tracker.libs[name]
|
32
|
+
else
|
33
|
+
@current_class = { :name => name,
|
34
|
+
:parent => class_name(exp[2]),
|
35
|
+
:includes => [],
|
36
|
+
:public => {},
|
37
|
+
:private => {},
|
38
|
+
:protected => {},
|
39
|
+
:src => exp,
|
40
|
+
:file => @file_name }
|
41
|
+
|
42
|
+
@tracker.libs[name] = @current_class
|
43
|
+
end
|
44
|
+
|
45
|
+
exp[3] = process exp[3]
|
46
|
+
|
47
|
+
if outer_class
|
48
|
+
@current_class = outer_class
|
49
|
+
else
|
50
|
+
@current_class = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
exp
|
54
|
+
end
|
55
|
+
|
56
|
+
def process_module exp
|
57
|
+
name = class_name(exp[1])
|
58
|
+
|
59
|
+
if @current_module
|
60
|
+
outer_class = @current_module
|
61
|
+
name = (outer_class[:name].to_s + "::" + name.to_s).to_sym
|
62
|
+
end
|
63
|
+
|
64
|
+
if @current_class
|
65
|
+
name = (@current_class[:name].to_s + "::" + name.to_s).to_sym
|
66
|
+
end
|
67
|
+
|
68
|
+
if @tracker.libs[name]
|
69
|
+
@current_module = @tracker.libs[name]
|
70
|
+
else
|
71
|
+
@current_module = { :name => name,
|
72
|
+
:includes => [],
|
73
|
+
:public => {},
|
74
|
+
:private => {},
|
75
|
+
:protected => {},
|
76
|
+
:src => exp }
|
77
|
+
|
78
|
+
@tracker.libs[name] = @current_module
|
79
|
+
end
|
80
|
+
|
81
|
+
exp[2] = process exp[2]
|
82
|
+
|
83
|
+
if outer_class
|
84
|
+
@current_module = outer_class
|
85
|
+
else
|
86
|
+
@current_module = nil
|
87
|
+
end
|
88
|
+
|
89
|
+
exp
|
90
|
+
end
|
91
|
+
|
92
|
+
def process_defn exp
|
93
|
+
exp[0] = :methdef
|
94
|
+
exp[3] = @alias_processor.process_safely process(exp[3]), SexpProcessor::Environment.new
|
95
|
+
|
96
|
+
if @current_class
|
97
|
+
@current_class[:public][exp[1]] = exp[3]
|
98
|
+
elsif @current_module
|
99
|
+
@current_module[:public][exp[1]] = exp[3]
|
100
|
+
end
|
101
|
+
|
102
|
+
exp
|
103
|
+
end
|
104
|
+
|
105
|
+
def process_defs exp
|
106
|
+
exp[0] = :selfdef
|
107
|
+
exp[4] = @alias_processor.process_safely process(exp[4]), SexpProcessor::Environment.new
|
108
|
+
|
109
|
+
if @current_class
|
110
|
+
@current_class[:public][exp[2]] = exp[4]
|
111
|
+
elsif @current_module
|
112
|
+
@current_module[:public][exp[3]] = exp[4]
|
113
|
+
end
|
114
|
+
|
115
|
+
exp
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'processors/base_processor'
|
2
|
+
|
3
|
+
#Processes models. Puts results in tracker.models
|
4
|
+
class ModelProcessor < BaseProcessor
|
5
|
+
def initialize tracker
|
6
|
+
super
|
7
|
+
@model = nil
|
8
|
+
@current_method = nil
|
9
|
+
@visibility = :public
|
10
|
+
@file_name = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
#Process model source
|
14
|
+
def process_model src, file_name = nil
|
15
|
+
@file_name = file_name
|
16
|
+
process src
|
17
|
+
end
|
18
|
+
|
19
|
+
#s(:class, NAME, PARENT, s(:scope ...))
|
20
|
+
def process_class exp
|
21
|
+
if @model
|
22
|
+
warn "[Notice] Skipping inner class: #{class_name exp[1]}" if OPTIONS[:debug]
|
23
|
+
ignore
|
24
|
+
else
|
25
|
+
@model = { :name => class_name(exp[1]),
|
26
|
+
:parent => class_name(exp[2]),
|
27
|
+
:includes => [],
|
28
|
+
:public => {},
|
29
|
+
:private => {},
|
30
|
+
:protected => {},
|
31
|
+
:options => {},
|
32
|
+
:file => @file_name }
|
33
|
+
@tracker.models[@model[:name]] = @model
|
34
|
+
res = process exp[3]
|
35
|
+
@model = nil
|
36
|
+
res
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
#Handle calls outside of methods,
|
41
|
+
#such as include, attr_accessible, private, etc.
|
42
|
+
def process_call exp
|
43
|
+
return exp unless @model
|
44
|
+
target = exp[1]
|
45
|
+
if sexp? target
|
46
|
+
target = process target
|
47
|
+
end
|
48
|
+
|
49
|
+
method = exp[2]
|
50
|
+
args = exp[3]
|
51
|
+
|
52
|
+
#Methods called inside class definition
|
53
|
+
#like attr_* and other settings
|
54
|
+
if @current_method.nil? and target.nil?
|
55
|
+
if args.length == 1 #actually, empty
|
56
|
+
case method
|
57
|
+
when :private, :protected, :public
|
58
|
+
@visibility = method
|
59
|
+
else
|
60
|
+
#??
|
61
|
+
end
|
62
|
+
else
|
63
|
+
case method
|
64
|
+
when :include
|
65
|
+
@model[:includes] << class_name(args[1]) if @model
|
66
|
+
when :attr_accessible
|
67
|
+
@model[:attr_accessible] ||= []
|
68
|
+
args = args[1..-1].map do |e|
|
69
|
+
e[1]
|
70
|
+
end
|
71
|
+
|
72
|
+
@model[:attr_accessible].concat args
|
73
|
+
else
|
74
|
+
if @model
|
75
|
+
@model[:options][method] ||= []
|
76
|
+
@model[:options][method] << process(args)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
ignore
|
81
|
+
else
|
82
|
+
call = Sexp.new :call, target, method, process(args)
|
83
|
+
call.line(exp.line)
|
84
|
+
call
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
#Add method definition to tracker
|
89
|
+
def process_defn exp
|
90
|
+
return exp unless @model
|
91
|
+
name = exp[1]
|
92
|
+
|
93
|
+
@current_method = name
|
94
|
+
res = Sexp.new :methdef, name, process(exp[2]), process(exp[3][1])
|
95
|
+
res.line(exp.line)
|
96
|
+
@current_method = nil
|
97
|
+
if @model
|
98
|
+
list = @model[@visibility]
|
99
|
+
list[name] = res
|
100
|
+
end
|
101
|
+
res
|
102
|
+
end
|
103
|
+
|
104
|
+
#Add method definition to tracker
|
105
|
+
def process_defs exp
|
106
|
+
return exp unless @model
|
107
|
+
name = exp[2]
|
108
|
+
|
109
|
+
if exp[1].node_type == :self
|
110
|
+
target = @model[:name]
|
111
|
+
else
|
112
|
+
target = class_name exp[1]
|
113
|
+
end
|
114
|
+
|
115
|
+
@current_method = name
|
116
|
+
res = Sexp.new :selfdef, target, name, process(exp[3]), process(exp[4][1])
|
117
|
+
res.line(exp.line)
|
118
|
+
@current_method = nil
|
119
|
+
if @model
|
120
|
+
@model[@visibility][name] = res unless @model.nil?
|
121
|
+
end
|
122
|
+
res
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ruby2ruby'
|
3
|
+
require 'util'
|
4
|
+
|
5
|
+
#Produces formatted output strings from Sexps.
|
6
|
+
#Recommended usage is
|
7
|
+
#
|
8
|
+
# OutputProcessor.new.format(Sexp.new(:str, "hello"))
|
9
|
+
class OutputProcessor < Ruby2Ruby
|
10
|
+
include Util
|
11
|
+
|
12
|
+
#Copies +exp+ and then formats it.
|
13
|
+
def format exp
|
14
|
+
process exp.deep_clone
|
15
|
+
end
|
16
|
+
|
17
|
+
alias process_safely format
|
18
|
+
|
19
|
+
def process exp
|
20
|
+
begin
|
21
|
+
super exp if sexp? exp and not exp.empty?
|
22
|
+
rescue Exception => e
|
23
|
+
warn "While formatting #{exp}: #{e}\n#{e.backtrace.join("\n")}" if OPTIONS[:debug]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def process_call exp
|
28
|
+
if exp[0].is_a? Symbol
|
29
|
+
target = exp[0]
|
30
|
+
|
31
|
+
method = exp[1]
|
32
|
+
|
33
|
+
args = process exp[2]
|
34
|
+
|
35
|
+
out = nil
|
36
|
+
|
37
|
+
if method == :[]
|
38
|
+
if target
|
39
|
+
out = "#{target}[#{args}]"
|
40
|
+
else
|
41
|
+
raise Exception.new("Not sure what to do with access and no target: #{exp}")
|
42
|
+
end
|
43
|
+
else
|
44
|
+
if target
|
45
|
+
out = "#{target}.#{method}(#{args})"
|
46
|
+
else
|
47
|
+
out = "#{method}(#{args})"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
exp.clear
|
51
|
+
out
|
52
|
+
else
|
53
|
+
super exp
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def process_lvar exp
|
58
|
+
out = "(local #{exp[0]})"
|
59
|
+
exp.clear
|
60
|
+
out
|
61
|
+
end
|
62
|
+
|
63
|
+
def process_ignore exp
|
64
|
+
exp.clear
|
65
|
+
"[ignored]"
|
66
|
+
end
|
67
|
+
|
68
|
+
def process_params exp
|
69
|
+
exp.clear
|
70
|
+
"params"
|
71
|
+
end
|
72
|
+
|
73
|
+
def process_session exp
|
74
|
+
exp.clear
|
75
|
+
"session"
|
76
|
+
end
|
77
|
+
|
78
|
+
def process_cookies exp
|
79
|
+
exp.clear
|
80
|
+
"cookies"
|
81
|
+
end
|
82
|
+
|
83
|
+
def process_string_interp exp
|
84
|
+
out = '"'
|
85
|
+
exp.each do |e|
|
86
|
+
if e.is_a? String
|
87
|
+
out << e
|
88
|
+
else
|
89
|
+
res = process e
|
90
|
+
out << res unless res == ""
|
91
|
+
end
|
92
|
+
end
|
93
|
+
out << '"'
|
94
|
+
exp.clear
|
95
|
+
out
|
96
|
+
end
|
97
|
+
|
98
|
+
def process_string_eval exp
|
99
|
+
out = "\#{#{process(exp[0])}}"
|
100
|
+
exp.clear
|
101
|
+
out
|
102
|
+
end
|
103
|
+
|
104
|
+
def process_dxstr exp
|
105
|
+
out = "`"
|
106
|
+
out << exp.map! do |e|
|
107
|
+
if e.is_a? String
|
108
|
+
e
|
109
|
+
elsif string? e
|
110
|
+
e[1]
|
111
|
+
else
|
112
|
+
process e
|
113
|
+
end
|
114
|
+
end.join
|
115
|
+
exp.clear
|
116
|
+
out << "`"
|
117
|
+
end
|
118
|
+
|
119
|
+
def process_rlist exp
|
120
|
+
out = exp.map do |e|
|
121
|
+
res = process e
|
122
|
+
if res == ""
|
123
|
+
nil
|
124
|
+
else
|
125
|
+
res
|
126
|
+
end
|
127
|
+
end.compact.join("\n")
|
128
|
+
exp.clear
|
129
|
+
out
|
130
|
+
end
|
131
|
+
|
132
|
+
def process_call_with_block exp
|
133
|
+
call = process exp[0]
|
134
|
+
block = process exp[1] if exp[1]
|
135
|
+
out = "#{call} do\n #{block}\n end"
|
136
|
+
exp.clear
|
137
|
+
out
|
138
|
+
end
|
139
|
+
|
140
|
+
def process_output exp
|
141
|
+
out = if exp[0].node_type == :str
|
142
|
+
""
|
143
|
+
else
|
144
|
+
res = process exp[0]
|
145
|
+
|
146
|
+
if res == ""
|
147
|
+
""
|
148
|
+
else
|
149
|
+
"[Output] #{res}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
exp.clear
|
153
|
+
out
|
154
|
+
end
|
155
|
+
|
156
|
+
def process_format exp
|
157
|
+
out = if exp[0].node_type == :str or exp[0].node_type == :ignore
|
158
|
+
""
|
159
|
+
else
|
160
|
+
res = process exp[0]
|
161
|
+
|
162
|
+
if res == ""
|
163
|
+
""
|
164
|
+
else
|
165
|
+
"[Format] #{res}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
exp.clear
|
169
|
+
out
|
170
|
+
end
|
171
|
+
|
172
|
+
def process_format_escaped exp
|
173
|
+
out = if exp[0].node_type == :str or exp[0].node_type == :ignore
|
174
|
+
""
|
175
|
+
else
|
176
|
+
res = process exp[0]
|
177
|
+
|
178
|
+
if res == ""
|
179
|
+
""
|
180
|
+
else
|
181
|
+
"[Escaped] #{res}"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
exp.clear
|
185
|
+
out
|
186
|
+
end
|
187
|
+
|
188
|
+
def process_const exp
|
189
|
+
if exp[0] == Tracker::UNKNOWN_MODEL
|
190
|
+
exp.clear
|
191
|
+
"(Unresolved Model)"
|
192
|
+
else
|
193
|
+
super exp
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def process_render exp
|
198
|
+
exp[1] = process exp[1] if sexp? exp[1]
|
199
|
+
exp[2] = process exp[2] if sexp? exp[2]
|
200
|
+
out = "render(#{exp[0]} => #{exp[1]}, #{exp[2]})"
|
201
|
+
exp.clear
|
202
|
+
out
|
203
|
+
end
|
204
|
+
end
|