Skip to content

Commit 336ebff

Browse files
committed
Add validation for integer values to prevent overflow in dataset creation and writing
1 parent 9263751 commit 336ebff

3 files changed

Lines changed: 55 additions & 2 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ Unsupported at this stage:
1818
- string dataset read/write
1919
- multidimensional array write
2020

21+
Integer datasets and integer attributes currently use native C `int` under the hood.
22+
Values outside that range are rejected with `HDF5::Error` to avoid silent overflow.
23+
2124
## Supported HDF5 Versions
2225

2326
- HDF5 1.10

lib/hdf5/dataset.rb

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def normalize_data(data)
4646

4747
def datatype_id_for(data)
4848
if data.all? { |value| value.is_a?(Integer) }
49+
validate_native_int_range!(data)
4950
HDF5::FFI.H5T_NATIVE_INT
5051
elsif data.all? { |value| value.is_a?(Numeric) }
5152
HDF5::FFI.H5T_NATIVE_DOUBLE
@@ -66,7 +67,23 @@ def buffer_for(data)
6667
buffer
6768
end
6869

69-
private :normalize_data, :datatype_id_for, :buffer_for
70+
def native_int_bounds
71+
bits = ::FFI.type_size(:int) * 8
72+
max = (1 << (bits - 1)) - 1
73+
min = -(1 << (bits - 1))
74+
[min, max]
75+
end
76+
77+
def validate_native_int_range!(values)
78+
min, max = native_int_bounds
79+
out_of_range = values.find { |value| value < min || value > max }
80+
return unless out_of_range
81+
82+
raise HDF5::Error,
83+
"Integer value #{out_of_range} is outside native int range (#{min}..#{max}). Use a smaller value."
84+
end
85+
86+
private :normalize_data, :datatype_id_for, :buffer_for, :native_int_bounds, :validate_native_int_range!
7087

7188
private
7289

@@ -87,8 +104,8 @@ def attrs
87104

88105
def write(data)
89106
values = self.class.send(:normalize_data, data)
90-
buffer = self.class.send(:buffer_for, values)
91107
mem_type_id = self.class.send(:datatype_id_for, values)
108+
buffer = self.class.send(:buffer_for, values)
92109
status = HDF5::FFI.H5Dwrite(@dataset_id, mem_type_id, HDF5::DEFAULT_PROPERTY_LIST, HDF5::DEFAULT_PROPERTY_LIST,
93110
HDF5::DEFAULT_PROPERTY_LIST, buffer)
94111
raise HDF5::Error, 'Failed to write dataset' if status < 0

test/hdf5_test.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,39 @@ class HDF5Test < Test::Unit::TestCase
7474
end
7575
end
7676

77+
test 'rejects integer values outside native int range on create' do
78+
min = -(1 << (::FFI.type_size(:int) * 8 - 1))
79+
max = (1 << (::FFI.type_size(:int) * 8 - 1)) - 1
80+
81+
Dir.mktmpdir do |dir|
82+
path = File.join(dir, 'overflow-create.h5')
83+
84+
HDF5::File.create(path) do |file|
85+
error = assert_raise(HDF5::Error) do
86+
file.create_dataset('too_large', [max + 1])
87+
end
88+
assert_include(error.message, "#{min}..#{max}")
89+
end
90+
end
91+
end
92+
93+
test 'rejects integer values outside native int range on write' do
94+
min = -(1 << (::FFI.type_size(:int) * 8 - 1))
95+
max = (1 << (::FFI.type_size(:int) * 8 - 1)) - 1
96+
97+
Dir.mktmpdir do |dir|
98+
path = File.join(dir, 'overflow-write.h5')
99+
100+
HDF5::File.create(path) do |file|
101+
dataset = file.create_dataset('ints', [1, 2, 3])
102+
error = assert_raise(HDF5::Error) do
103+
dataset.write([min - 1])
104+
end
105+
assert_include(error.message, "#{min}..#{max}")
106+
end
107+
end
108+
end
109+
77110
test 'block API closes resources automatically' do
78111
Dir.mktmpdir do |dir|
79112
path = File.join(dir, 'block.h5')

0 commit comments

Comments
 (0)