From 70aacdcbd16c55d6c9526948367f29d5c9e99f0b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 6 Aug 2021 04:43:37 +0800 Subject: [PATCH 1/3] Add `Slice#unsafe_slice_of`, `#to_unsafe_bytes`, `#to_bytes` --- spec/std/slice_spec.cr | 51 +++++++++++++++++++++++++++++++++++++++ src/slice.cr | 54 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/spec/std/slice_spec.cr b/spec/std/slice_spec.cr index 9273608d051f..e8cbb2bd3f01 100644 --- a/spec/std/slice_spec.cr +++ b/spec/std/slice_spec.cr @@ -346,6 +346,57 @@ describe "Slice" do end end + describe "#unsafe_slice_of" do + it "reinterprets a slice's elements" do + slice = Bytes.new(10) { |i| i.to_u8 + 1 } + + {% if IO::ByteFormat::SystemEndian == IO::ByteFormat::LittleEndian %} + slice.unsafe_slice_of(Int16).should eq(Int16.slice(0x0201, 0x0403, 0x0605, 0x0807, 0x0A09)) + slice.unsafe_slice_of(Int32).should eq(Int32.slice(0x04030201, 0x08070605)) + + slice.unsafe_slice_of(UInt64)[0] = 0x1122_3344_5566_7788_u64 + slice.should eq(Bytes[0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x09, 0x0A]) + {% else %} + slice.unsafe_slice_of(Int16).should eq(Int16.slice(0x0102, 0x0304, 0x0506, 0x0708, 0x090A)) + slice.unsafe_slice_of(Int32).should eq(Int32.slice(0x01020304, 0x05060708)) + + slice.unsafe_slice_of(UInt64)[0] = 0x1122_3344_5566_7788_u64 + slice.should eq(Bytes[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x09, 0x0A]) + {% end %} + end + end + + describe "#to_unsafe_bytes" do + it "reinterprets a slice's elements as bytes" do + slice = Slice[0x01020304, -0x01020304] + bytes = slice.to_unsafe_bytes + + {% if IO::ByteFormat::SystemEndian == IO::ByteFormat::LittleEndian %} + bytes.should eq(Bytes[0x04, 0x03, 0x02, 0x01, 0xFC, 0xFC, 0xFD, 0xFE]) + bytes[3] = 0x55 + slice[0].should eq(0x55020304) + {% else %} + bytes.should eq(Bytes[0x01, 0x02, 0x03, 0x04, 0xFE, 0xFD, 0xFC, 0xFC]) + bytes[3] = 0x55 + slice[0].should eq(0x01020355) + {% end %} + end + end + + describe "#to_bytes" do + it "reinterprets a slice's elements as read-only bytes" do + slice = Slice[0x01020304, -0x01020304] + bytes = slice.to_bytes + bytes.read_only?.should be_true + + {% if IO::ByteFormat::SystemEndian == IO::ByteFormat::LittleEndian %} + bytes.should eq(Bytes[0x04, 0x03, 0x02, 0x01, 0xFC, 0xFC, 0xFD, 0xFE]) + {% else %} + bytes.should eq(Bytes[0x01, 0x02, 0x03, 0x04, 0xFE, 0xFD, 0xFC, 0xFC]) + {% end %} + end + end + it "does hexstring" do slice = Bytes.new(4) { |i| i.to_u8 + 1 } slice.hexstring.should eq("01020304") diff --git a/src/slice.cr b/src/slice.cr index 4c1894132bd6..067e3f72767f 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -497,6 +497,60 @@ struct Slice(T) to_s(io) end + # Returns a new `Slice` pointing at the same contents as `self`, but + # reinterpreted as elements of the given *type*. + # + # The returned slice never refers to more memory than `self`; if the last + # bytes of `self` do not fit into a `U`, they are excluded from the returned + # slice. + # + # WARNING: This method is **unsafe**: elements are reinterpreted using + # `#unsafe_as`, and the resulting slice may not be properly aligned. + # + # ``` + # # assume little-endian system + # bytes = Bytes[0x01, 0x02, 0x03, 0x04, 0xFF, 0xFE] + # bytes.unsafe_slice_of(Int8) # => Slice[1_i8, 2_i8, 3_i8, 4_i8, -1_i8, -2_i8] + # bytes.unsafe_slice_of(Int16) # => Slice[513_i16, 1027_i16, -257_i16] + # bytes.unsafe_slice_of(Int32) # => Slice[0x04030201] + # ``` + def unsafe_slice_of(type : U.class) : Slice(U) forall U + Slice.new(to_unsafe.unsafe_as(Pointer(U)), bytesize // sizeof(U), read_only: @read_only) + end + + # Returns a new `Bytes` pointing at the same contents as `self`. + # + # WARNING: This method is **unsafe**: the returned slice is writable if `self` + # is also writable, and modifications through the returned slice may violate + # the binary representations of Crystal objects. + # + # ``` + # # assume little-endian system + # ints = Slice[0x01020304, 0x05060708] + # bytes = ints.to_unsafe_bytes # => Bytes[0x04, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05] + # bytes[2] = 0xAD + # ints # => Slice[0x01AD0304, 0x05060708] + # ``` + def to_unsafe_bytes : Bytes + unsafe_slice_of(UInt8) + end + + # Returns a new read-only `Bytes` pointing at the same contents as `self`. + # + # This method is always safe to call; if `self` refers to a valid memory + # region, then it is always possible to address the individual bytes of that + # memory region. + # + # ``` + # # assume little-endian system + # ints = Slice[0x01020304, 0x05060708] + # bytes = ints.to_bytes # => Bytes[0x04, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05] + # bytes[2] = 0xAD # raises Exception + # ``` + def to_bytes : Bytes + Slice.new(to_unsafe.as(UInt8*), bytesize, read_only: true) + end + # Returns a hexstring representation of this slice, assuming it's # a `Slice(UInt8)`. # From dd69b5b27ff6093984fcdc1e8844d09d41cb5dc6 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 28 Oct 2021 18:51:44 +0800 Subject: [PATCH 2/3] Allow non-`Bytes` receiver in `#hexdump` and `#hexstring` --- spec/std/slice_spec.cr | 106 ++++++++++++++++++++++++++--------------- src/slice.cr | 45 +++++++++-------- 2 files changed, 91 insertions(+), 60 deletions(-) diff --git a/spec/std/slice_spec.cr b/spec/std/slice_spec.cr index e8cbb2bd3f01..517f7c78919b 100644 --- a/spec/std/slice_spec.cr +++ b/spec/std/slice_spec.cr @@ -397,45 +397,73 @@ describe "Slice" do end end - it "does hexstring" do - slice = Bytes.new(4) { |i| i.to_u8 + 1 } - slice.hexstring.should eq("01020304") - end - - it "does hexdump for empty slice" do - Bytes.empty.hexdump.should eq("") - - io = IO::Memory.new - Bytes.empty.hexdump(io).should eq(0) - io.to_s.should eq("") - end - - it "does hexdump" do - slice = Bytes.new(96) { |i| i.to_u8 + 32 } - assert_prints slice.hexdump, <<-EOF - 00000000 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ - 00000010 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? - 00000020 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO - 00000030 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\\]^_ - 00000040 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno - 00000050 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~.\n - EOF - - plus = Bytes.new(101) { |i| i.to_u8 + 32 } - assert_prints plus.hexdump, <<-EOF - 00000000 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ - 00000010 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? - 00000020 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO - 00000030 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\\]^_ - 00000040 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno - 00000050 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~. - 00000060 80 81 82 83 84 .....\n - EOF - - num = Bytes.new(10) { |i| i.to_u8 + 48 } - assert_prints num.hexdump, <<-EOF - 00000000 30 31 32 33 34 35 36 37 38 39 0123456789\n - EOF + describe "#hexstring" do + it "works for Bytes" do + slice = Bytes.new(4) { |i| i.to_u8 + 1 } + slice.hexstring.should eq("01020304") + end + + it "works for other element types" do + slice = Slice[0x01020304, -0x01020304] + + {% if IO::ByteFormat::SystemEndian == IO::ByteFormat::LittleEndian %} + slice.hexstring.should eq("04030201fcfcfdfe") + {% else %} + slice.hexstring.should eq("01020304fefdfcfc") + {% end %} + end + end + + describe "#hexdump" do + it "works for empty slice" do + Bytes.empty.hexdump.should eq("") + + io = IO::Memory.new + Bytes.empty.hexdump(io).should eq(0) + io.to_s.should eq("") + end + + it "works for Bytes" do + slice = Bytes.new(96) { |i| i.to_u8 + 32 } + assert_prints slice.hexdump, <<-EOF + 00000000 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ + 00000010 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? + 00000020 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO + 00000030 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\\]^_ + 00000040 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno + 00000050 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~.\n + EOF + + plus = Bytes.new(101) { |i| i.to_u8 + 32 } + assert_prints plus.hexdump, <<-EOF + 00000000 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ + 00000010 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? + 00000020 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO + 00000030 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\\]^_ + 00000040 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno + 00000050 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~. + 00000060 80 81 82 83 84 .....\n + EOF + + num = Bytes.new(10) { |i| i.to_u8 + 48 } + assert_prints num.hexdump, <<-EOF + 00000000 30 31 32 33 34 35 36 37 38 39 0123456789\n + EOF + end + + it "works for other element types" do + slice = Slice[0x31323334, 0x61626364] + + {% if IO::ByteFormat::SystemEndian == IO::ByteFormat::LittleEndian %} + assert_prints slice.hexdump, <<-EOF + 00000000 34 33 32 31 64 63 62 61 4321dcba\n + EOF + {% else %} + assert_prints slice.hexdump, <<-EOF + 00000000 31 32 33 34 61 62 63 64 1234abcd\n + EOF + {% end %} + end end it_iterates "#each", [1, 2, 3], Slice[1, 2, 3].each diff --git a/src/slice.cr b/src/slice.cr index 067e3f72767f..3d989922c1a4 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -551,17 +551,16 @@ struct Slice(T) Slice.new(to_unsafe.as(UInt8*), bytesize, read_only: true) end - # Returns a hexstring representation of this slice, assuming it's - # a `Slice(UInt8)`. + # Returns a hexstring representation of this slice. If `self` is not a + # `Bytes`, its contents are reinterpreted using `#to_bytes`. # # ``` - # slice = UInt8.slice(97, 62, 63, 8, 255) - # slice.hexstring # => "613e3f08ff" + # # assume little-endian system + # UInt8.slice(97, 62, 63, 8, 255).hexstring # => "613e3f08ff" + # Int16.slice(97, 62, 1000, -2).hexstring # => "61003e00e803feff" # ``` def hexstring : String - self.as(Slice(UInt8)) - - str_size = size * 2 + str_size = bytesize * 2 String.new(str_size) do |buffer| hexstring(buffer) {str_size, str_size} @@ -570,10 +569,8 @@ struct Slice(T) # :nodoc: def hexstring(buffer) : Nil - self.as(Slice(UInt8)) - offset = 0 - each do |v| + to_bytes.each do |v| buffer[offset] = to_hex(v >> 4) buffer[offset + 1] = to_hex(v & 0x0f) offset += 2 @@ -582,20 +579,24 @@ struct Slice(T) nil end - # Returns a hexdump of this slice, assuming it's a `Slice(UInt8)`. + # Returns a hexdump of this slice. If `self` is not a `Bytes`, its contents + # are reinterpreted using `#to_bytes`. + # # This method is specially useful for debugging binary data and # incoming/outgoing data in protocols. # # ``` # slice = UInt8.slice(97, 62, 63, 8, 255) # slice.hexdump # => "00000000 61 3e 3f 08 ff a>?..\n" + # + # # assume little-endian system + # slice = Int16.slice(97, 62, 1000, -2) + # slice.hexdump # => "00000000 61 00 3e 00 e8 03 fe ff a.>.....\n" # ``` def hexdump : String - self.as(Slice(UInt8)) - return "" if empty? - full_lines, leftover = size.divmod(16) + full_lines, leftover = bytesize.divmod(16) if leftover == 0 str_size = full_lines * 77 else @@ -606,7 +607,7 @@ struct Slice(T) pos = 0 offset = 0 - while pos < size + while pos < bytesize # Ensure we don't write outside the buffer: # slower, but safer (speed is not very important when hexdump is used) hexdump_line(Slice.new(buf + offset, {77, str_size - offset}.min), pos) @@ -618,7 +619,9 @@ struct Slice(T) end end - # Writes a hexdump of this slice, assuming it's a `Slice(UInt8)`, to the given *io*. + # Writes a hexdump of this slice to the given *io*. If `self` is not a + # `Bytes`, its contents are reinterpreted using `#to_bytes`. + # # This method is specially useful for debugging binary data and # incoming/outgoing data in protocols. # @@ -635,8 +638,6 @@ struct Slice(T) # 00000000 61 3e 3f 08 ff a>?.. # ``` def hexdump(io : IO) - self.as(Slice(UInt8)) - return 0 if empty? line = uninitialized UInt8[77] @@ -644,7 +645,7 @@ struct Slice(T) count = 0 pos = 0 - while pos < size + while pos < bytesize line_bytes = hexdump_line(line_slice, pos) io.write_string(line_slice[0, line_bytes]) count += line_bytes @@ -656,6 +657,8 @@ struct Slice(T) end private def hexdump_line(line, start_pos) + bytes = to_unsafe.as(UInt8*) + hex_offset = 10 ascii_offset = 60 @@ -667,8 +670,8 @@ struct Slice(T) pos = start_pos 16.times do |i| - break if pos >= size - v = unsafe_fetch(pos) + break if pos >= bytesize + v = bytes[pos] pos += 1 line[hex_offset] = to_hex(v >> 4) From 669162de174424345573cc7ea0970cf9f303048c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 7 Dec 2021 11:27:42 +0800 Subject: [PATCH 3/3] remove `#to_bytes` --- spec/std/slice_spec.cr | 38 ---------------------- src/slice.cr | 74 +++++++++++++++++++++++------------------- 2 files changed, 41 insertions(+), 71 deletions(-) diff --git a/spec/std/slice_spec.cr b/spec/std/slice_spec.cr index 517f7c78919b..93545cd2fc82 100644 --- a/spec/std/slice_spec.cr +++ b/spec/std/slice_spec.cr @@ -383,35 +383,11 @@ describe "Slice" do end end - describe "#to_bytes" do - it "reinterprets a slice's elements as read-only bytes" do - slice = Slice[0x01020304, -0x01020304] - bytes = slice.to_bytes - bytes.read_only?.should be_true - - {% if IO::ByteFormat::SystemEndian == IO::ByteFormat::LittleEndian %} - bytes.should eq(Bytes[0x04, 0x03, 0x02, 0x01, 0xFC, 0xFC, 0xFD, 0xFE]) - {% else %} - bytes.should eq(Bytes[0x01, 0x02, 0x03, 0x04, 0xFE, 0xFD, 0xFC, 0xFC]) - {% end %} - end - end - describe "#hexstring" do it "works for Bytes" do slice = Bytes.new(4) { |i| i.to_u8 + 1 } slice.hexstring.should eq("01020304") end - - it "works for other element types" do - slice = Slice[0x01020304, -0x01020304] - - {% if IO::ByteFormat::SystemEndian == IO::ByteFormat::LittleEndian %} - slice.hexstring.should eq("04030201fcfcfdfe") - {% else %} - slice.hexstring.should eq("01020304fefdfcfc") - {% end %} - end end describe "#hexdump" do @@ -450,20 +426,6 @@ describe "Slice" do 00000000 30 31 32 33 34 35 36 37 38 39 0123456789\n EOF end - - it "works for other element types" do - slice = Slice[0x31323334, 0x61626364] - - {% if IO::ByteFormat::SystemEndian == IO::ByteFormat::LittleEndian %} - assert_prints slice.hexdump, <<-EOF - 00000000 34 33 32 31 64 63 62 61 4321dcba\n - EOF - {% else %} - assert_prints slice.hexdump, <<-EOF - 00000000 31 32 33 34 61 62 63 64 1234abcd\n - EOF - {% end %} - end end it_iterates "#each", [1, 2, 3], Slice[1, 2, 3].each diff --git a/src/slice.cr b/src/slice.cr index 3d989922c1a4..6b65dc344634 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -506,6 +506,8 @@ struct Slice(T) # # WARNING: This method is **unsafe**: elements are reinterpreted using # `#unsafe_as`, and the resulting slice may not be properly aligned. + # Additionally, the same elements may produce different results depending on + # the system endianness. # # ``` # # assume little-endian system @@ -522,7 +524,8 @@ struct Slice(T) # # WARNING: This method is **unsafe**: the returned slice is writable if `self` # is also writable, and modifications through the returned slice may violate - # the binary representations of Crystal objects. + # the binary representations of Crystal objects. Additionally, the same + # elements may produce different results depending on the system endianness. # # ``` # # assume little-endian system @@ -535,32 +538,23 @@ struct Slice(T) unsafe_slice_of(UInt8) end - # Returns a new read-only `Bytes` pointing at the same contents as `self`. + # Returns a hexstring representation of this slice. # - # This method is always safe to call; if `self` refers to a valid memory - # region, then it is always possible to address the individual bytes of that - # memory region. + # `self` must be a `Slice(UInt8)`. To call this method on other `Slice`s, + # `#to_unsafe_bytes` should be used first. # # ``` - # # assume little-endian system - # ints = Slice[0x01020304, 0x05060708] - # bytes = ints.to_bytes # => Bytes[0x04, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05] - # bytes[2] = 0xAD # raises Exception - # ``` - def to_bytes : Bytes - Slice.new(to_unsafe.as(UInt8*), bytesize, read_only: true) - end - - # Returns a hexstring representation of this slice. If `self` is not a - # `Bytes`, its contents are reinterpreted using `#to_bytes`. + # UInt8.slice(97, 62, 63, 8, 255).hexstring # => "613e3f08ff" # - # ``` # # assume little-endian system - # UInt8.slice(97, 62, 63, 8, 255).hexstring # => "613e3f08ff" - # Int16.slice(97, 62, 1000, -2).hexstring # => "61003e00e803feff" + # Int16.slice(97, 62, 1000, -2).to_unsafe_bytes.hexstring # => "61003e00e803feff" # ``` def hexstring : String - str_size = bytesize * 2 + {% unless T == UInt8 %} + {% raise "Can only call `#hexstring` on Slice(UInt8), not #{@type}" %} + {% end %} + + str_size = size * 2 String.new(str_size) do |buffer| hexstring(buffer) {str_size, str_size} @@ -569,8 +563,12 @@ struct Slice(T) # :nodoc: def hexstring(buffer) : Nil + {% unless T == UInt8 %} + {% raise "Can only call `#hexstring` on Slice(UInt8), not #{@type}" %} + {% end %} + offset = 0 - to_bytes.each do |v| + each do |v| buffer[offset] = to_hex(v >> 4) buffer[offset + 1] = to_hex(v & 0x0f) offset += 2 @@ -579,8 +577,10 @@ struct Slice(T) nil end - # Returns a hexdump of this slice. If `self` is not a `Bytes`, its contents - # are reinterpreted using `#to_bytes`. + # Returns a hexdump of this slice. + # + # `self` must be a `Slice(UInt8)`. To call this method on other `Slice`s, + # `#to_unsafe_bytes` should be used first. # # This method is specially useful for debugging binary data and # incoming/outgoing data in protocols. @@ -591,12 +591,16 @@ struct Slice(T) # # # assume little-endian system # slice = Int16.slice(97, 62, 1000, -2) - # slice.hexdump # => "00000000 61 00 3e 00 e8 03 fe ff a.>.....\n" + # slice.to_unsafe_bytes.hexdump # => "00000000 61 00 3e 00 e8 03 fe ff a.>.....\n" # ``` def hexdump : String + {% unless T == UInt8 %} + {% raise "Can only call `#hexdump` on Slice(UInt8), not #{@type}" %} + {% end %} + return "" if empty? - full_lines, leftover = bytesize.divmod(16) + full_lines, leftover = size.divmod(16) if leftover == 0 str_size = full_lines * 77 else @@ -607,7 +611,7 @@ struct Slice(T) pos = 0 offset = 0 - while pos < bytesize + while pos < size # Ensure we don't write outside the buffer: # slower, but safer (speed is not very important when hexdump is used) hexdump_line(Slice.new(buf + offset, {77, str_size - offset}.min), pos) @@ -619,8 +623,10 @@ struct Slice(T) end end - # Writes a hexdump of this slice to the given *io*. If `self` is not a - # `Bytes`, its contents are reinterpreted using `#to_bytes`. + # Writes a hexdump of this slice to the given *io*. + # + # `self` must be a `Slice(UInt8)`. To call this method on other `Slice`s, + # `#to_unsafe_bytes` should be used first. # # This method is specially useful for debugging binary data and # incoming/outgoing data in protocols. @@ -638,6 +644,10 @@ struct Slice(T) # 00000000 61 3e 3f 08 ff a>?.. # ``` def hexdump(io : IO) + {% unless T == UInt8 %} + {% raise "Can only call `#hexdump` on Slice(UInt8), not #{@type}" %} + {% end %} + return 0 if empty? line = uninitialized UInt8[77] @@ -645,7 +655,7 @@ struct Slice(T) count = 0 pos = 0 - while pos < bytesize + while pos < size line_bytes = hexdump_line(line_slice, pos) io.write_string(line_slice[0, line_bytes]) count += line_bytes @@ -657,8 +667,6 @@ struct Slice(T) end private def hexdump_line(line, start_pos) - bytes = to_unsafe.as(UInt8*) - hex_offset = 10 ascii_offset = 60 @@ -670,8 +678,8 @@ struct Slice(T) pos = start_pos 16.times do |i| - break if pos >= bytesize - v = bytes[pos] + break if pos >= size + v = unsafe_fetch(pos) pos += 1 line[hex_offset] = to_hex(v >> 4)