Class: SeccompTools::Asm::Scanner
- Inherits:
-
Object
- Object
- SeccompTools::Asm::Scanner
- Defined in:
- lib/seccomp-tools/asm/scanner.rb
Overview
Converts text to array of tokens.
Maintains columns and rows to have informative error messages.
Internally used by SeccompAsmParser.
Constant Summary collapse
- KEYWORDS =
Keywords with special meanings in our assembly. Keywords are all case-insensitive.
%w[a x if else return mem args args_h data len sys_number arch instruction_pointer].freeze
- KEYWORD_MATCHER =
Regexp for matching keywords.
/\A\b(#{KEYWORDS.join('|')})\b/i- ACTIONS =
Action strings can be used in a return statement. Actions must be in upper case. See Const::BPF::ACTION.
Const::BPF::ACTION.keys.map(&:to_s)
- ACTION_MATCHER =
Regexp for matching actions.
/\A\b(#{ACTIONS.join('|')})\b/- AUDIT_ARCHES =
Special constants for checking the current architecture. See Const::Audit::ARCH. These constants are case-insensitive.
Const::Audit::ARCH.keys
- AUDIT_ARCH_MATCHER =
Regexp for matching arch values.
/\A\b(#{AUDIT_ARCHES.join('|')})\b/i- COMPARE =
Comparisons.
%w[== != >= <= > <].freeze
- COMPARE_MATCHER =
Regexp for matching comparisons.
/\A(#{COMPARE.join('|')})/- ALU_OP =
All valid arithmetic operators.
%w[+ - * / | ^ << >>].freeze
- ALU_OP_MATCHER =
Regexp for matching ALU operators.
/\A(#{ALU_OP.map { |o| ::Regexp.escape(o) }.join('|')})/- ARCHES =
Supported architectures
SeccompTools::Syscall::ABI.keys.map(&:to_s)
- TAB_WIDTH =
Let tab on terminal be 4 spaces wide.
4
Instance Attribute Summary collapse
-
#syscalls ⇒ Object
readonly
Returns the value of attribute syscalls.
Instance Method Summary collapse
- #format_error(tok, msg) ⇒ String
-
#initialize(str, arch, filename: nil) ⇒ Scanner
constructor
A new instance of Scanner.
- #scan ⇒ Array<Token>
-
#validate ⇒ Array<Token>
Same as #validate! but returns the array of errors instead of raising an exception.
-
#validate! ⇒ self
Scans the whole string and raises errors when there are unrecognized tokens.
Constructor Details
#initialize(str, arch, filename: nil) ⇒ Scanner
Returns a new instance of Scanner.
48 49 50 51 52 53 54 55 56 |
# File 'lib/seccomp-tools/asm/scanner.rb', line 48 def initialize(str, arch, filename: nil) @filename = filename || '<inline>' @str = str @syscalls = begin; Const::Syscall.const_get(arch.to_s.upcase); rescue NameError; []; end @syscall_all = ARCHES.each_with_object({}) do |ar, memo| memo.merge!(Const::Syscall.const_get(ar.to_s.upcase)) end.keys end |
Instance Attribute Details
#syscalls ⇒ Object (readonly)
Returns the value of attribute syscalls.
16 17 18 |
# File 'lib/seccomp-tools/asm/scanner.rb', line 16 def syscalls @syscalls end |
Instance Method Details
#format_error(tok, msg) ⇒ String
144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/seccomp-tools/asm/scanner.rb', line 144 def format_error(tok, msg) @lines = @str.lines unless defined?(@lines) line = @lines[tok.line] line = line[0..-2] if line.end_with?("\n") line = line.gsub("\t", ' ' * TAB_WIDTH) <<-EOS #{@filename}:#{tok.line + 1}:#{tok.col + 1} #{msg} #{line} #{' ' * calculate_spaces(@lines[tok.line][0...tok.col]) + '^' * tok.str.size} EOS end |
#scan ⇒ Array<Token>
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 |
# File 'lib/seccomp-tools/asm/scanner.rb', line 76 def scan return @tokens if defined?(@tokens) @tokens = [] row = 0 col = 0 str = @str add_token = ->(sym, s, c = col) { @tokens.push(Token.new(sym, s, row, c)) } # define a helper because it's commonly used - add a token with matched string, bump col with string size bump_vars = lambda { col += ::Regexp.last_match(0).size str = ::Regexp.last_match.post_match } add_token_def = lambda do |sym| add_token.call(sym, ::Regexp.last_match(0)) bump_vars.call end syscalls = @syscalls.keys.map { |s| ::Regexp.escape(s) }.join('|') syscall_matcher = ::Regexp.compile("\\A\\b(#{syscalls})\\b") syscall_all_matcher = ::Regexp.compile("\\A(#{ARCHES.join('|')})\\.(#{@syscall_all.join('|')})\\b") until str.empty? case str when /\A\n+/ # Don't push newline as the first token add_token.call(:NEWLINE, ::Regexp.last_match(0)) unless @tokens.empty? row += ::Regexp.last_match(0).size col = 0 str = ::Regexp.last_match.post_match when /\A\s+/, /\A#.*/ then bump_vars.call when /\A(\w+):/ add_token.call(:SYMBOL, ::Regexp.last_match(1)) bump_vars.call when /\A(goto|jmp|jump)\s+(\w+)\b/i add_token.call(:GOTO, ::Regexp.last_match(1), col + ::Regexp.last_match.begin(1)) add_token.call(:GOTO_SYMBOL, ::Regexp.last_match(2), col + ::Regexp.last_match.begin(2)) bump_vars.call when KEYWORD_MATCHER then add_token_def.call(::Regexp.last_match(0).upcase.to_sym) when ACTION_MATCHER then add_token_def.call(:ACTION) when AUDIT_ARCH_MATCHER then add_token_def.call(:ARCH_VAL) when syscall_matcher, syscall_all_matcher then add_token_def.call(:SYSCALL) when /\A-?0x[0-9a-f]+\b/ then add_token_def.call(:HEX_INT) when /\A-?[0-9]+\b/ then add_token_def.call(:INT) when ALU_OP_MATCHER then add_token_def.call(:ALU_OP) when COMPARE_MATCHER then add_token_def.call(:COMPARE) # '&' is in both compare and ALU op category, handle it here when /\A(\(|\)|=|\[|\]|&|!)/ then add_token_def.call(::Regexp.last_match(0)) when /\A\?\s*(?<jt>\w+)\s*:\s*(?<jf>\w+)/ %i[jt jf].each do |s| add_token.call(:GOTO_SYMBOL, ::Regexp.last_match(s), col + ::Regexp.last_match.begin(s)) end bump_vars.call when /\A([^\s]+)(\s?)/ # unrecognized token - match until \s last = ::Regexp.last_match(1) add_token.call(:unknown, last) col += last.size str = ::Regexp.last_match(2) + ::Regexp.last_match.post_match end end @tokens end |
#validate ⇒ Array<Token>
Same as #validate! but returns the array of errors instead of raising an exception.
71 72 73 |
# File 'lib/seccomp-tools/asm/scanner.rb', line 71 def validate scan.select { |t| t.sym == :unknown } end |
#validate! ⇒ self
Scans the whole string and raises errors when there are unrecognized tokens.
61 62 63 64 65 66 |
# File 'lib/seccomp-tools/asm/scanner.rb', line 61 def validate! errors = validate return self if errors.empty? raise UnrecognizedTokenError, errors.map { |e| format_error(e, "unknown token #{e.str.inspect}") }.join("\n") end |