spektr 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yaml +32 -0
  3. data/.gitignore +9 -0
  4. data/.travis.yml +6 -0
  5. data/CHANGELOG.md +3 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +4 -0
  8. data/Gemfile.lock +134 -0
  9. data/Guardfile +45 -0
  10. data/LICENSE.txt +27 -0
  11. data/README.md +70 -0
  12. data/Rakefile +10 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/bin/spektr +7 -0
  16. data/lib/spektr/app.rb +209 -0
  17. data/lib/spektr/checks/base.rb +151 -0
  18. data/lib/spektr/checks/basic_auth.rb +27 -0
  19. data/lib/spektr/checks/basic_auth_timing.rb +24 -0
  20. data/lib/spektr/checks/command_injection.rb +48 -0
  21. data/lib/spektr/checks/content_tag_xss.rb +54 -0
  22. data/lib/spektr/checks/cookie_serialization.rb +21 -0
  23. data/lib/spektr/checks/create_with.rb +27 -0
  24. data/lib/spektr/checks/csrf.rb +25 -0
  25. data/lib/spektr/checks/csrf_setting.rb +39 -0
  26. data/lib/spektr/checks/default_routes.rb +43 -0
  27. data/lib/spektr/checks/deserialize.rb +62 -0
  28. data/lib/spektr/checks/detailed_exceptions.rb +29 -0
  29. data/lib/spektr/checks/digest_dos.rb +28 -0
  30. data/lib/spektr/checks/dynamic_finders.rb +26 -0
  31. data/lib/spektr/checks/evaluation.rb +25 -0
  32. data/lib/spektr/checks/file_access.rb +38 -0
  33. data/lib/spektr/checks/file_disclosure.rb +25 -0
  34. data/lib/spektr/checks/filter_skipping.rb +29 -0
  35. data/lib/spektr/checks/header_dos.rb +20 -0
  36. data/lib/spektr/checks/i18n_xss.rb +20 -0
  37. data/lib/spektr/checks/json_encoding.rb +23 -0
  38. data/lib/spektr/checks/json_entity_escape.rb +30 -0
  39. data/lib/spektr/checks/json_parsing.rb +47 -0
  40. data/lib/spektr/checks/link_to_href.rb +35 -0
  41. data/lib/spektr/checks/mass_assignment.rb +42 -0
  42. data/lib/spektr/checks/send.rb +24 -0
  43. data/lib/spektr/checks/sqli.rb +52 -0
  44. data/lib/spektr/checks/xss.rb +49 -0
  45. data/lib/spektr/checks.rb +9 -0
  46. data/lib/spektr/cli.rb +53 -0
  47. data/lib/spektr/erubi.rb +78 -0
  48. data/lib/spektr/exp/assignment.rb +20 -0
  49. data/lib/spektr/exp/base.rb +32 -0
  50. data/lib/spektr/exp/const.rb +7 -0
  51. data/lib/spektr/exp/definition.rb +32 -0
  52. data/lib/spektr/exp/ivasign.rb +7 -0
  53. data/lib/spektr/exp/lvasign.rb +7 -0
  54. data/lib/spektr/exp/send.rb +135 -0
  55. data/lib/spektr/exp/xstr.rb +12 -0
  56. data/lib/spektr/processors/base.rb +80 -0
  57. data/lib/spektr/processors/class_processor.rb +25 -0
  58. data/lib/spektr/targets/base.rb +119 -0
  59. data/lib/spektr/targets/config.rb +6 -0
  60. data/lib/spektr/targets/controller.rb +74 -0
  61. data/lib/spektr/targets/model.rb +6 -0
  62. data/lib/spektr/targets/routes.rb +38 -0
  63. data/lib/spektr/targets/view.rb +34 -0
  64. data/lib/spektr/version.rb +3 -0
  65. data/lib/spektr/warning.rb +23 -0
  66. data/lib/spektr.rb +120 -0
  67. data/spektr.gemspec +49 -0
  68. metadata +362 -0
@@ -0,0 +1,25 @@
1
+ module Spektr
2
+ class Checks
3
+ class FileDisclosure < Base
4
+
5
+ def initialize(app, target)
6
+ super
7
+ @name = "File existence disclosure"
8
+ @type = "Information Disclosure"
9
+ @targets = ["Spektr::Targets::Base"]
10
+ end
11
+
12
+ def run
13
+ return unless super
14
+ config = @app.production_config.find_calls(:serve_static_assets=).first
15
+ if config && config.arguments.first.type == :true
16
+ warn! "root", self, nil, "File existence disclosure vulnerability"
17
+ end
18
+ end
19
+
20
+ def should_run?
21
+ app_version_between?("2.0.0", "2.3.18") || app_version_between?("3.0.0", "3.2.20") || app_version_between?("4.0.0", "4.0.11") || app_version_between?("4.1.0", "4.1.7")
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ module Spektr
2
+ class Checks
3
+ class FilterSkipping < Base
4
+ def initialize(app, target)
5
+ super
6
+ @name = "Default routes filter skipping"
7
+ @type = "Default Routes"
8
+ @targets = ["Spektr::Targets::Routes"]
9
+ end
10
+
11
+ def run
12
+ return unless super
13
+ calls = %w{ match get post put delete }.inject([]) do |memo, method|
14
+ memo.concat @target.find_calls(method.to_sym)
15
+ memo
16
+ end
17
+ calls.each do |call|
18
+ if !call.arguments.empty? && (call.arguments.first.name.include?(":action") or call.arguments.first.name.include?("*action"))
19
+ warn! @target, self, call.location, "CVE-2011-2929 Rails versions before 3.0.10 have a vulnerability which allows filters to be bypassed"
20
+ end
21
+ end
22
+ end
23
+
24
+ def should_run?
25
+ app_version_between?("3.0.0", "3.0.9")
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ module Spektr
2
+ class Checks
3
+ class HeaderDos < Base
4
+
5
+ def initialize(app, target)
6
+ super
7
+ @name = "HTTP MIME type header DoS (CVE-2013-6414)"
8
+ @type = "Denial of Service"
9
+ @targets = ["Spektr::Targets::Base"]
10
+ end
11
+
12
+ def run
13
+ return unless super
14
+ if app_version_between?("3.0.0", "3.2.15")
15
+ warn! "root", self, nil, "CVE_2013_6414"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Spektr
2
+ class Checks
3
+ class I18nXss < Base
4
+
5
+ def initialize(app, target)
6
+ super
7
+ @name = "XSS in i18n (CVE-2013-4491)"
8
+ @type = "Cross-Site Scripting"
9
+ @targets = ["Spektr::Targets::Base"]
10
+ end
11
+
12
+ def run
13
+ return unless super
14
+ if app_version_between?("3.0.6", "3.2.15") || app_version_between?("4.0.0", "4.0.1")
15
+ warn! "root", self, nil, "I18n has a Cross-Site Scripting vulnerability (CVE_2013_4491)"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ module Spektr
2
+ class Checks
3
+ class JsonEncoding < Base
4
+ def initialize(app, target)
5
+ super
6
+ @name = "XSS by missing JSON encoding"
7
+ @type = "Cross-Site Scripting"
8
+ @targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::Routes", "Spektr::Targets::View"]
9
+ end
10
+
11
+ def run
12
+ return unless super
13
+ if app_version_between?("4.1.0", "4.1.10") || app_version_between?("4.2.0", "4.2.1")
14
+ if calls = @target.find_calls(:to_json).any? || calls = @target.find_calls(:encode).any?
15
+ warn! @target, self, calls.first.location, "Cross-Site Scripting CVE_2015_3226"
16
+ else
17
+ warn! "root", self, nil, "Cross-Site Scripting CVE_2015_3226"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ module Spektr
2
+ class Checks
3
+ class JsonEntityEscape < Base
4
+
5
+ def initialize(app, target)
6
+ super
7
+ @name = "HTML escaping is disabled for JSON output"
8
+ @type = "Cross-Site Scripting"
9
+ @targets = ["Spektr::Targets::Config", "Spektr::Targets::Base"]
10
+ end
11
+
12
+ def run
13
+ return unless super
14
+ if @app.production_config
15
+ config = @app.production_config.find_calls(:escape_html_entities_in_json=).first
16
+ end
17
+ if config and config.receiver.expanded == "config.active_support" && config.arguments.first.type == :false
18
+ warn! @app.production_config.path, self, nil, "HTML entities in JSON are not escaped by default"
19
+ end
20
+ ['ActiveSupport', 'ActiveSupport.JSON.Encoding'].each do |receiver|
21
+ calls = @target.find_calls(:escape_html_entities_in_json=, receiver)
22
+ if calls.any?
23
+ warn! @target, self, calls.first.location, "HTML entities in JSON are not escaped by default"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,47 @@
1
+ module Spektr
2
+ class Checks
3
+ class JsonParsing < Base
4
+ def initialize(app, target)
5
+ super
6
+ @name = "JSON parsing vulnerability"
7
+ @type = "Remote Code Execution"
8
+ @targets = ["Spektr::Targets::Base"]
9
+ end
10
+
11
+ def run
12
+ return unless super
13
+ check_cve_2013_0333
14
+ check_cve_2013_0269
15
+ end
16
+
17
+ def check_cve_2013_0333
18
+ return unless app_version_between?("0.0.0", "2.3.15") || app_version_between?("3.0.0", "3.0.19")
19
+ if @app.has_gem?("yajl")
20
+ warn! "root", self, nil, "Remote Code Execution CVE_2013_0333"
21
+ end
22
+ uses_json_gem?
23
+ end
24
+
25
+ def uses_json_gem?
26
+ @target.find_calls(:backend=).each do |call|
27
+ if call.receiver.expanded == "ActiveSupport.JSON" && call.arguments.first&.name == :JSONGem
28
+ warn! @target, self, call.location, "Remote Code Execution CVE_2013_0333"
29
+ end
30
+ end
31
+ end
32
+
33
+ def check_cve_2013_0269
34
+ ["json", "json_pure"].each do |gem_name|
35
+ if g = @app.gem_specs&.find { |g| g.name == gem_name }
36
+ if version_between?("1.7.0", "1.7.6", g.version)
37
+ warn! "Gemfile", self, nil, "Unsafe Object Creation Vulnerability in the #{g.name} gem"
38
+ end
39
+ if version_between?("0", "1.5.4", g.version) || version_between?("1.6.0", "1.6.7", g.version)
40
+ warn! "Gemfile", self, nil, "Unsafe Object Creation Vulnerability in the #{g.name} gem"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,35 @@
1
+ module Spektr
2
+ class Checks
3
+ class LinkToHref < Base
4
+ def initialize(app, target)
5
+ super
6
+ @name = "XSS in href param of link_to"
7
+ @type = "Cross-Site Scripting"
8
+ @targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::View"]
9
+ end
10
+
11
+ # TODO: check for user supplied model attributes too
12
+ def run
13
+ return unless super
14
+ block_locations = []
15
+ @target.find_calls_with_block(:link_to).each do |call|
16
+ block_locations << call.location
17
+ next unless call.arguments.first
18
+ ::Spektr.logger.debug "#{@target.path} #{call.location.line} #{call.arguments.first.inspect}"
19
+ if user_input? call.arguments.first.type, call.arguments.first.name, call.arguments.first.ast, call.arguments.first
20
+ warn! @target, self, call.location, "Cross-Site Scripting: Unsafe user supplied value in link_to"
21
+ end
22
+ end
23
+
24
+ @target.find_calls(:link_to).each do |call|
25
+ next if block_locations.include? call.location
26
+ ::Spektr.logger.debug "#{@target.path} #{call.location.line} #{call.arguments[1].inspect}"
27
+ next unless call.arguments[1] || call.arguments[1]&.name =~ /_url$|_path$/
28
+ if user_input? call.arguments[1].type, call.arguments[1].name, call.arguments[1].ast, call.arguments[1]
29
+ warn! @target, self, call.location, "Cross-Site Scripting: Unsafe user supplied value in link_to"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,42 @@
1
+ module Spektr
2
+ class Checks
3
+ class MassAssignment < Base
4
+
5
+ # TODO: Make this better
6
+ def initialize(app, target)
7
+ super
8
+ @name = "Mass Assignment"
9
+ @type = "Input Validation"
10
+ @targets = ["Spektr::Targets::Controller"]
11
+ end
12
+
13
+ def run
14
+ return unless super
15
+ model_names = @app.models.collect(&:name)
16
+ calls = []
17
+ model_names.each do |receiver|
18
+ [:new, :build, :create].each do |method|
19
+ calls.concat @target.find_calls(method, receiver)
20
+ end
21
+ end
22
+ calls.each do |call|
23
+ argument = call.arguments.first
24
+ next if argument.nil?
25
+ ::Spektr.logger.debug "Mass assignment check at #{call.location.line}"
26
+ if user_input?(argument.type, argument.name, call.ast)
27
+ # we check for permit! separately
28
+ next if argument.ast.children[1] == :permit!
29
+ # check for permit with arguments
30
+ next if argument.ast.children[1] == :permit && argument.ast.children[2]
31
+ warn! @target, self, call.location, "Mass assignment"
32
+ end
33
+ end
34
+ @target.find_calls(:permit!).each do |call|
35
+ if call.arguments.none?
36
+ warn! @target, self, call.location, "permit! allows any keys, use it with caution!", :medium
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ module Spektr
2
+ class Checks
3
+ class Send < Base
4
+ def initialize(app, target)
5
+ super
6
+ @name = "Dangerous send"
7
+ @type = "Dangerous send"
8
+ @targets = ["Spektr::Targets::Base", "Spektr::Targets::Model", "Spektr::Targets::Controller", "Spektr::Targets::Routes", "Spektr::Targets::View"]
9
+ end
10
+
11
+ def run
12
+ return unless super
13
+ [:send, :try, :__send__, :public_send].each do |method|
14
+ @target.find_calls(method).each do |call|
15
+ argument = call.arguments.first
16
+ if user_input?(argument.type, argument.name, argument.ast)
17
+ warn! @target, self, call.location, "User supplied value in send"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,52 @@
1
+ module Spektr
2
+ class Checks
3
+ class Sqli < Base
4
+ def initialize(app, target)
5
+ super
6
+ @name = "SQL Injection"
7
+ @name = "SQL Injection"
8
+ @targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::Model"]
9
+ end
10
+
11
+ def run
12
+ return unless super
13
+
14
+ [
15
+ :average, :count, :maximum, :minimum, :sum, :exists?,
16
+ :find_by, :find_by!, :find_or_create_by, :find_or_create_by!,
17
+ :find_or_initialize_by, :from, :group, :having, :join, :lock,
18
+ :where, :not, :select, :rewhere, :reselect, :update_all
19
+
20
+ ].each do |m|
21
+ @target.find_calls(m).each do |call|
22
+ check_argument(call.arguments.first, m, call)
23
+ end
24
+ end
25
+ [:calculate].each do |m|
26
+ @target.find_calls(m).each do |call|
27
+ check_argument(call.arguments[1], m, call)
28
+ end
29
+ end
30
+
31
+ [:delete_by, :destroy_by].each do |m|
32
+ @target.find_calls(m).each do |call|
33
+ if call.arguments.first
34
+ check_argument(call.arguments.first, m, call)
35
+ end
36
+ call.options.values.each do |option|
37
+ check_argument(@target.ast_to_exp(option.key), m, call)
38
+ check_argument(@target.ast_to_exp(option.value), m, call)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def check_argument(argument, method, call)
45
+ return if argument.nil?
46
+ if user_input?(argument.type, argument.name, argument.ast, argument)
47
+ warn! @target, self, call.location, "Possible SQL Injection at #{method}"
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,49 @@
1
+ module Spektr
2
+ class Checks
3
+ class Xss < Base
4
+ def initialize(app, target)
5
+ super
6
+ @name = "XSS"
7
+ @type = "Cross-Site Scripting"
8
+ @targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::View"]
9
+ end
10
+
11
+ # TODO: tests for haml, xml, js
12
+ # TODO: add check for raw calls
13
+ def run
14
+ return unless super
15
+ calls = @target.find_calls(:safe_expr_append=)
16
+ calls.concat(@target.find_calls(:raw))
17
+ calls.each do |call|
18
+ call.arguments.each do |argument|
19
+ if user_input?(argument.type, argument.name, argument.ast)
20
+ warn! @target, self, call.location, "Cross-Site Scripting: Unescaped user input"
21
+ end
22
+ if model_attribute?(argument)
23
+ warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute #{argument.name}"
24
+ end
25
+ end
26
+ end
27
+ calls.each do |call|
28
+ call.arguments.each do |argument|
29
+ if user_input?(argument.type, argument.name, argument.ast)
30
+ warn! @target, self, call.location, "Cross-Site Scripting: Unescaped user input"
31
+ end
32
+ if model_attribute?(argument)
33
+ warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute #{argument.name}"
34
+ end
35
+ end
36
+ end
37
+ calls = @target.find_calls(:html_safe)
38
+ calls.each do |call|
39
+ if user_input?(call.receiver.type, call.receiver.name, call.receiver.ast, call.receiver)
40
+ warn! @target, self, call.location, "Cross-Site Scripting: Unescaped user input"
41
+ end
42
+ if model_attribute?(call.receiver)
43
+ warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute #{call.receiver.name}"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,9 @@
1
+ module Spektr
2
+ class Checks
3
+ def self.load(only = false)
4
+ Checks.constants.select do |c|
5
+ Checks.const_get(c).is_a?(Class) && (!only || only && only.to_s == c.to_s)
6
+ end.map { |c| Checks.const_get(c) }
7
+ end
8
+ end
9
+ end
data/lib/spektr/cli.rb ADDED
@@ -0,0 +1,53 @@
1
+ require 'tty/option'
2
+ require 'json'
3
+ module Spektr
4
+ class Cli
5
+ include TTY::Option
6
+ usage do
7
+ program 'Spektr'
8
+ command 'scan'
9
+ desc 'Find vulnerabilities in ruby code'
10
+ end
11
+
12
+ argument :root do
13
+ optional
14
+ desc 'Path to application root'
15
+ end
16
+
17
+ flag :output_format do
18
+ long '--output_format string'
19
+ desc 'output format terminal or json'
20
+ default 'terminal'
21
+ end
22
+
23
+ flag :check do
24
+ long '--check string'
25
+ desc 'run this single check'
26
+ end
27
+
28
+ flag :debug do
29
+ long '--debug'
30
+ short '-d'
31
+ desc 'output debug logs to STDOUT'
32
+ end
33
+
34
+ flag :help do
35
+ short '-h'
36
+ long '--help'
37
+ desc 'Print usage'
38
+ end
39
+
40
+ def scan
41
+ if params[:help]
42
+ print help
43
+ exit
44
+ else
45
+ report = Spektr.run(params[:root], params[:output_format], params[:debug], params[:check])
46
+ case params[:output_format]
47
+ when 'json'
48
+ puts JSON.pretty_generate report
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,78 @@
1
+ require "erubi"
2
+ # This is a copy of module ActionView::Template::Handlers::ERB::Erubi
3
+ module Spektr
4
+ class Erubi < ::Erubi::Engine
5
+ def initialize(input, properties = {})
6
+ @newline_pending = 0
7
+
8
+ # Dup properties so that we don't modify argument
9
+ properties = Hash[properties]
10
+ properties[:preamble] = ""
11
+ properties[:postamble] = "@output_buffer.to_s"
12
+ properties[:bufvar] = "@output_buffer"
13
+ properties[:escapefunc] = ""
14
+
15
+ super
16
+ end
17
+
18
+ def evaluate(action_view_erb_handler_context)
19
+ src = @src
20
+ view = Class.new(ActionView::Base) {
21
+ include action_view_erb_handler_context._routes.url_helpers
22
+ class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", defined?(@filename) ? @filename : "(erubi)", 0)
23
+ }.empty
24
+ view._run(:_template, nil, {}, ActionView::OutputBuffer.new)
25
+ end
26
+
27
+ private
28
+ def add_text(text)
29
+ return if text.empty?
30
+
31
+ if text == "\n"
32
+ @newline_pending += 1
33
+ else
34
+ src << "@output_buffer.safe_append='"
35
+ src << "\n" * @newline_pending if @newline_pending > 0
36
+ src << text.gsub(/['\\]/, '\\\\\&')
37
+ src << "'.freeze;"
38
+
39
+ @newline_pending = 0
40
+ end
41
+ end
42
+
43
+ BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
44
+
45
+ def add_expression(indicator, code)
46
+ flush_newline_if_pending(src)
47
+
48
+ if (indicator == "==") || @escape
49
+ src << "@output_buffer.safe_expr_append="
50
+ else
51
+ src << "@output_buffer.append="
52
+ end
53
+
54
+ if BLOCK_EXPR.match?(code)
55
+ src << " " << code
56
+ else
57
+ src << "(" << code << ");"
58
+ end
59
+ end
60
+
61
+ def add_code(code)
62
+ flush_newline_if_pending(src)
63
+ super
64
+ end
65
+
66
+ def add_postamble(_)
67
+ flush_newline_if_pending(src)
68
+ super
69
+ end
70
+
71
+ def flush_newline_if_pending(src)
72
+ if @newline_pending > 0
73
+ src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
74
+ @newline_pending = 0
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,20 @@
1
+ module Spektr
2
+ module Exp
3
+ module Assignment
4
+ def user_input?
5
+ if ast.children[1].type == :send
6
+ _send = Send.new(ast.children[1])
7
+ name = if _send.receiver && _send.receiver.type == :send
8
+ _send.receiver.name
9
+ else
10
+ nil
11
+ end
12
+ if [:params, :cookies, :request].include? name
13
+ return true
14
+ end
15
+ end
16
+ false
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ module Spektr
2
+ module Exp
3
+ class Base
4
+ attr_accessor :ast, :name, :type, :options, :arguments, :body, :location
5
+
6
+ def initialize(ast)
7
+ @ast = ast
8
+ @type = ast.type
9
+ @location = ast.location
10
+ @name = ast.children.first
11
+ @options = {}
12
+ @arguments = []
13
+ @body = []
14
+ end
15
+
16
+ def send?
17
+ is_a? Send
18
+ end
19
+
20
+ include AST::Processor::Mixin
21
+
22
+ def process(ast)
23
+ return unless ast.respond_to?(:to_ast)
24
+ super
25
+ end
26
+
27
+ def handler_missing(node)
28
+ # puts "handler missing for #{node.type}"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ module Spektr
2
+ module Exp
3
+ class Const < Base
4
+ include Spektr::Exp::Assignment
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,32 @@
1
+ module Spektr
2
+ module Exp
3
+ class Definition < Base
4
+ attr_accessor :private, :protected
5
+
6
+ def initialize(ast)
7
+ super
8
+ process @ast.children
9
+ end
10
+
11
+ def process(ast)
12
+ ast&.each do |node|
13
+ next unless Parser::AST::Node === node
14
+ case node.type
15
+ when :args
16
+ node.children.each do |argument|
17
+ @arguments << argument.children.first
18
+ end
19
+ when :begin
20
+ process(node.children)
21
+ when :lvasgn
22
+ @body << Lvasign.new(node)
23
+ when :ivasgn
24
+ @body << Ivasign.new(node)
25
+ when :send
26
+ @body << Send.new(node)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ module Spektr
2
+ module Exp
3
+ class Ivasign < Base
4
+ include Spektr::Exp::Assignment
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Spektr
2
+ module Exp
3
+ class Lvasign < Base
4
+ include Spektr::Exp::Assignment
5
+ end
6
+ end
7
+ end