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 the seccomp asm parser.
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
-
#arch ⇒ Symbol
readonly
The architecture this scanner was created with.
-
#syscalls ⇒ {Symbol => Integer}
readonly
Syscall name to number, for the architecture this scanner was created with.
Instance Method Summary collapse
-
#format_error(tok, msg) ⇒ String
Formats an error message that points at a token in the source.
-
#initialize(str, arch, filename: nil) ⇒ Scanner
constructor
Instantiates a Scanner object.
-
#scan ⇒ Array<Token>
Scans the whole string into tokens.
-
#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
Instantiates a SeccompTools::Asm::Scanner object.
58 59 60 61 62 63 64 65 66 67 |
# File 'lib/seccomp-tools/asm/scanner.rb', line 58 def initialize(str, arch, filename: nil) @filename = filename || '<inline>' @str = str @arch = arch @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
#arch ⇒ Symbol (readonly)
Returns The architecture this scanner was created with.
21 22 23 |
# File 'lib/seccomp-tools/asm/scanner.rb', line 21 def arch @arch end |
#syscalls ⇒ {Symbol => Integer} (readonly)
Returns Syscall name to number, for the architecture this scanner was created with.
18 19 20 |
# File 'lib/seccomp-tools/asm/scanner.rb', line 18 def syscalls @syscalls end |
Instance Method Details
#format_error(tok, msg) ⇒ String
Formats an error message that points at a token in the source.
176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/seccomp-tools/asm/scanner.rb', line 176 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>
Scans the whole string into tokens.
The result is memoized, so repeated calls are free.
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 |
# File 'lib/seccomp-tools/asm/scanner.rb', line 95 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.
85 86 87 |
# File 'lib/seccomp-tools/asm/scanner.rb', line 85 def validate scan.select { |t| t.sym == :unknown } end |
#validate! ⇒ self
Scans the whole string and raises errors when there are unrecognized tokens.
74 75 76 77 78 79 |
# File 'lib/seccomp-tools/asm/scanner.rb', line 74 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 |