detect invalid Pack/Unpack directives

This commit is contained in:
cgranleese-r7 2023-07-04 12:30:28 +01:00
parent 36e0d8f915
commit 61f70e09f6
3 changed files with 1071 additions and 0 deletions

View File

@ -22,6 +22,7 @@ require:
- ./lib/rubocop/cop/lint/module_disclosure_date_present.rb
- ./lib/rubocop/cop/lint/deprecated_gem_version.rb
- ./lib/rubocop/cop/lint/module_enforce_notes.rb
- ./lib/rubocop/cop/lint/detect_invalid_pack_directives.rb
Layout/SpaceBeforeBrackets:
Description: >-
@ -166,6 +167,9 @@ Layout/ModuleHashValuesOnSameLine:
Layout/ModuleDescriptionIndentation:
Enabled: true
Lint/DetectInvalidPackDirectives:
Enabled: true
Lint/ModuleDisclosureDateFormat:
Enabled: true

View File

@ -0,0 +1,217 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Lint
# Looks for invalid pack/unpack directives with Ruby 3.3.0 some directives
# that used to not raise errors, now will - context: https://bugs.ruby-lang.org/issues/19150:
# * Array#pack now raises ArgumentError for unknown directives
# * String#unpack now raises ArgumentError for unknown directives
#
# @example
# # bad
# ```
# 3.3.0-preview1 :003 > [0x1].pack('<L')
# <internal:pack>:8:in `pack': unknown pack directive '<' in '<L' (ArgumentError)
# ```
#
# # good
# ```
# 3.3.0-preview1 :001 > [0x1].pack('L<')
# => "\x01\x00\x00\x00"
# ```
class DetectInvalidPackDirectives < RuboCop::Cop::Base
# https://github.com/ruby/ruby/blob/7cfabe1acc55b24fc2c479a87efa71cf74e9e8fc/pack.c#L38
MODIFIABLE_DIRECTIVES = %w[s S i I l L q Q j J]
# https://github.com/ruby/ruby/blob/7cfabe1acc55b24fc2c479a87efa71cf74e9e8fc/pack.c#L298
ACCEPTABLE_DIRECTIVES = %w[U m A B H a A Z b B h H c C s S i I l L q Q j J n N v V f F e E d D g G x X @ % U u m M P p w]
# @param [RuboCop::AST::SendNode] node Node for the ruby `send` method
# @return [[RuboCop::AST::Node], raise] offense when an invalid directive is found, or raise if unexpected error found
def on_send(node)
_callee, method_name = *node
return unless %i[pack unpack pack1 unpack1].include?(method_name)
args = node.arguments
return if args.empty?
args.each do |arg|
next unless string_arg?(arg)
# if multiline arguments are passed
if arg.type == :dstr
idx = []
pack_directive = arg.children.map do |child|
if begin_arg?(child)
next
else
idx << child.children.first.length
child.children.join
end
end.join
# elsif single line arguments are passed
elsif arg.type == :str
pack_directive = arg.children.first
end
error = validate_directive(pack_directive)
if error.nil?
next
else
offense_range = get_error_range(arg, error[:index], idx)
return if offense_range.nil?
add_offense(offense_range, message: error[:message])
end
end
end
private
# Check if the pack directives are valid. See link for pack docs https://apidock.com/ruby/Array/pack
#
# Code based on https://github.com/ruby/ruby/blob/6391132c03ac08da0483adb986ff9a54e41f9e14/pack.c#L196
# adapted into Ruby
#
# @param [String] pack_directive The ruby pack/unpack directive to validate
# @return [Hash,nil] A hash with the message and index that the invalidate directive was found at, or nil.
def validate_directive(pack_directive)
# current pointer value
p = 0
# end of pointer range
pend = pack_directive.length
while p < pend
explicit_endian = 0
type_index = p
# get data type
type = pack_directive[type_index]
p += 1
if type.blank?
next
end
if type == '#'
p += 1 while p < pend && pack_directive[p] != "\n"
next
end
# Modifiers
loop do
case pack_directive[p]
when '_', '!'
if MODIFIABLE_DIRECTIVES.include?(type)
p += 1
else
return { message: "'#{pack_directive[p]}' allowed only after types #{MODIFIABLE_DIRECTIVES.join}", index: p }
end
when '<', '>'
unless MODIFIABLE_DIRECTIVES.include?(type)
return { message: "'#{pack_directive[p]}' allowed only after types #{MODIFIABLE_DIRECTIVES.join}", index: p }
end
if explicit_endian != 0
return { message: "Can't use both '<' and '>'.", index: p }
end
explicit_endian = pack_directive[p]
p += 1
else
break
end
end
# Data length
if pack_directive[p] == '*'
p += 1
elsif pack_directive[p]&.match?(/\d/)
p += 1 while pack_directive[p]&.match?(/\d/)
end
# Check type
unless ACCEPTABLE_DIRECTIVES.include?(type)
return { message: "unknown pack directive '#{type}' in '#{pack_directive}'", index: type_index }
end
end
nil
end
# Checks if the current node is of type `:str` or `:dstr` - `dstr` being multiline
#
# @param [RuboCop::AST::SendNode] node Node for the ruby `send` method
# @return [TrueClass, FalseClass]
def string_arg?(node)
node&.type == :str || node&.type == :dstr
end
# Check if the node if of type `:begin`
#
# @param [RuboCop::AST::SendNode] node Node for the ruby `send` method
# @return [TrueClass, FalseClass]
def begin_arg?(node)
node.type == :begin
end
# Get the range of the offense to more accurately raise offenses against specific directives
#
# @param [RuboCop::AST::DstrNode, RuboCop::AST::StrNode ] arg The node that need its range calculated
# @param [Integer] p The current pointer value
# @param [Array] idx An array holding to number of indexes for the node
# @return [Parser::Source::Range] The range of the node value
def get_error_range(arg, p, idx)
# Logic for multiline strings
if arg.type == :dstr
total = 0
index = 0
idx.each_with_index do |idx_length, count|
if total < p
total += idx_length
index = count
end
end
adjusted_index = p - idx[0..(index - 1)].sum
indexed_arg = arg.children[index]
if begin_arg?(indexed_arg)
return nil
else
newline_adjustment = indexed_arg.children.first[0..adjusted_index].scan(/[\n\t]/).count
end
# If there's opening quotes present, i.e. "a", instead of heredoc which doesn't have preceding opening quotes:
if indexed_arg.loc.begin
range_start = indexed_arg.loc.begin.end_pos + (p - adjusted_index)
else
expression = processed_source.raw_source[indexed_arg.loc.expression.begin.begin_pos...indexed_arg.loc.expression.end.end_pos]
if expression[/^\s+/].nil?
leading_whitespace_size = 0
else
leading_whitespace_size = expression[/^\s+/].length
end
adjusted_index += leading_whitespace_size
range_start = indexed_arg.loc.expression.begin_pos + (adjusted_index + newline_adjustment)
end
# Logic for single line strings
else
newline_adjustment = arg.children.first[0..p].scan(/[\n\t]/).count
range_start = arg.loc.begin.end_pos + (p + newline_adjustment)
end
range_end = range_start + 1
Parser::Source::Range.new(arg.loc.expression.source_buffer, range_start, range_end)
end
end
end
end
end

View File

@ -0,0 +1,850 @@
require 'spec_helper'
require 'rubocop/cop/lint/detect_invalid_pack_directives'
RSpec.describe RuboCop::Cop::Lint::DetectInvalidPackDirectives do
subject(:cop) { described_class.new(config) }
let(:empty_rubocop_config) { {} }
let(:config) { RuboCop::Config.new(empty_rubocop_config) }
let(:pack_directive) { "Q<" }
let(:pack_amount) { 2 }
let(:endian) {:little}
let(:packstr) {(endian == :little) ? 'v' : 'n'}
context 'when passed an unknown pack/unpacks directive' do
it 'detects the invalid directive' do
expect_offense(<<~RUBY)
[10].pack('_I')
^ unknown pack directive '_' in '_I'
RUBY
end
it 'detects the invalid directive' do
expect_offense(<<~RUBY)
[10].pack('<L<L<Q<Q<Q<La*<L<Q<Q<Q<Q<Q<Q<L<L<Q<L<L')
^ unknown pack directive '<' in '<L<L<Q<Q<Q<La*<L<Q<Q<Q<Q<Q<Q<L<L<Q<L<L'
RUBY
end
it 'detects the invalid directive' do
expect_offense(<<~RUBY)
[10].pack('<123456')
^ unknown pack directive '<' in '<123456'
RUBY
end
it 'detects the invalid directive' do
expect_offense(<<~RUBY)
[10].pack('<')
^ unknown pack directive '<' in '<'
RUBY
end
it 'detects the invalid directive' do
expect_offense(<<~RUBY)
[10].pack('<s')
^ unknown pack directive '<' in '<s'
RUBY
end
it 'detects the invalid directive' do
expect_offense(<<~RUBY)
[0x0123456789ABCDEF, 'foo'].pack('<Qa*')
^ unknown pack directive '<' in '<Qa*'
RUBY
end
it 'detects the invalid directive' do
expect_offense(<<~RUBY)
[112].pack('<I')
^ unknown pack directive '<' in '<I'
RUBY
end
it 'detects the invalid directive' do
expect_offense(<<~RUBY)
[1].pack('<ISSSSI')
^ unknown pack directive '<' in '<ISSSSI'
RUBY
end
it 'detects the invalid directive' do
expect_offense(<<~RUBY)
[200].pack('<L')
^ unknown pack directive '<' in '<L'
RUBY
end
it 'detects the invalid directive' do
expect_offense(<<~RUBY)
[400].pack('<S<S')
^ unknown pack directive '<' in '<S<S'
RUBY
end
it 'detects the invalid directive' do
expect_offense(<<~RUBY)
"foo".unpack("*V")
^ unknown pack directive '*' in '*V'
RUBY
end
it 'detects the invalid directive' do
expect_offense(<<~RUBY)
"foo".unpack("D<")
^ '<' allowed only after types sSiIlLqQjJ
RUBY
end
it 'detects the invalid directive' do
expect_offense(<<~RUBY)
"foo".unpack(%q{D<})
^ '<' allowed only after types sSiIlLqQjJ
RUBY
end
it 'detects the invalid directive' do
expect_offense(<<~'RUBY')
"foo".unpack("\tD<")
^ '<' allowed only after types sSiIlLqQjJ
RUBY
end
end
context 'when passed multiline strings with invalid pack/unpacks directives' do
it 'ignores comments in the format string and detects the invalid directive' do
expect_offense(<<~RUBY)
[9.3, 4.7].pack <<~EOF
# The first decimal value
D # first inline comment
# The second decimal value
D< # The second inline comment
^ '<' allowed only after types sSiIlLqQjJ
# Final comment
EOF
RUBY
end
it 'ignores comments in the format string and detects the invalid directive' do
expect_offense(<<~RUBY)
[9.3, 4.7].pack <<~EOF
# The first decimal value
D # first inline comment
# The second decimal value
D< # The second inline comment
^ '<' allowed only after types sSiIlLqQjJ
# Final comment
EOF
RUBY
end
it 'ignores comments in the format string and detects the invalid directive' do
expect_offense(<<~RUBY)
[9.3, 4.7].pack <<~EOF
# The first decimal value
D # first inline comment
# The second decimal value
D< # The second inline comment
^ '<' allowed only after types sSiIlLqQjJ
# Final comment
EOF
RUBY
end
it 'ignores comments in the format string and detects the invalid directive' do
expect_offense(<<~'RUBY')
[1,2,3].pack("D# some comment \nD# some comment \n# some comment \nD>")
^ '>' allowed only after types sSiIlLqQjJ
RUBY
end
it 'ignores comments in the format string and detects the invalid directive' do
expect_offense(<<~'RUBY')
[1,2,3].pack("D># some comment \nD# some comment \n# some comment \nD")
^ '>' allowed only after types sSiIlLqQjJ
RUBY
end
it 'ignores comments in the format string and detects the invalid directive' do
expect_offense(<<~'RUBY')
[9.3, 4.7].pack("D# some comment \nD<")
^ '<' allowed only after types sSiIlLqQjJ
RUBY
end
it 'raises an offense' do
expect_offense(<<-'RUBY')
[10].pack(
"Q" \
"D<"
^ '<' allowed only after types sSiIlLqQjJ
)
RUBY
end
end
context 'when passed string interpolation' do
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[1, 2].pack("@1#{'XV' * (2 * 2)}")
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<-'RUBY')
[9, 4].pack("@1#{'XV' * (pack_amount * pack_amount)}")
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<-'RUBY')
[9, 4].pack("I<c#{pack_amount}")
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<-'RUBY')
"\t\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00".unpack("#{pack_directive}@1")
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<-'RUBY')
options[idx, 4].unpack("#{packstr}2")
RUBY
end
end
it 'raises an offense' do
expect_no_offenses(<<-'RUBY')
"\t\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00".unpack("#{pack_directive}*")
RUBY
end
context 'when passed valid pack/unpacks directives' do
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
"a\x10\x10\x10".unpack('nCCnnQ>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
['abc'].pack('h3')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[1, 2].pack("C@3C")
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
['a' * 123].pack('h123')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
['abc', 'a'].pack('h3h')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('C')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('S')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('L')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('Q')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('J')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('c')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('s')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('l')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('q')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('j')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('S_')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('S!')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('I')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('J')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('I_')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('I!')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('L_')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('L!')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('Q_')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('Q!')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('J!')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('s_')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('s!')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('i')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('i_')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('i!')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('l')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('l!')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('q_')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('q!')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('j!')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('S>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('s>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('S!>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('s!>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('L>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('l>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('L!>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('l!>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('I!>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('i!>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('Q>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('q>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('Q!>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('q!>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('J>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('j>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('J!>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('j!>')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('S<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('s<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('S!<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('s!<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('L<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('l<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('L!<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('l!<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('I!<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('i!<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('Q<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('q<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('Q!<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('q!<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('J<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('j<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('J!<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('j!<')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('n')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('N')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('v')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('V')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('U')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack('w')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10.10].pack('D')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10.10].pack('d')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10.10].pack('F')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10.10].pack('f')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10.10].pack('E')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10.10].pack('e')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10.10].pack('G')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10.10].pack('g')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
['test'].pack('A')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
['test'].pack('a')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
['test'].pack('Z')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
['test'].pack('B')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
['test'].pack('b')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
['test'].pack('H')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
['test'].pack('h')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
['test'].pack('u')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
['test'].pack('M')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
['test'].pack('m')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
['test'].pack('p')
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
['test'].pack('P')
RUBY
end
end
context 'when passed multiline strings with valid pack/unpacks directives' do
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[9.3, 4.7].pack <<~EOF
# The first decimal value
D # first inline comment
# The second decimal value
S< # The second inline comment
# Final comment
EOF
RUBY
end
it 'does not raise an offence' do
expect_no_offenses(<<~RUBY)
[10].pack(
"Q" \
"L"
)
RUBY
end
it 'raises an offense' do
expect_no_offenses(<<-'RUBY')
[10].pack(
"Q" \
"<L"
)
RUBY
end
it 'ignores comments in the format string and detects the invalid directive' do
expect_no_offenses(<<~RUBY)
[9.3, 4.7].pack("D# some comment S<")
RUBY
end
end
end