string_bits 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -2
  3. data/ext/string_bits/string_bits.c +467 -298
  4. metadata +4 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88122353506d55eaecdcdccc56fb8c1d6e07bc7eae76b46c01cdc98890395653
4
- data.tar.gz: 2fade95ae4b0f862d6a9b96884a9fec2faf5916859b8a5251f3fd4ad567bf40d
3
+ metadata.gz: c21407d901f599aa0d33fe0bad85d39f4729da08c656cab3556fc087198394f2
4
+ data.tar.gz: e4c8912125bfd9f4a6fcfce0be0d78f528f0bb87369064cfc1c96b4d3f5b7ac9
5
5
  SHA512:
6
- metadata.gz: 8fb4e9b166f936c1525fdfc86044baa1619caae3c80c067191b92c58adec30274c6a52fa1e488c95d3e213dcdce5b2de4bbbbb31ed56d65b26a81f28165cb2fb
7
- data.tar.gz: e8b09ea3c141ac04dfbf2b44ce747e0c8122eb5f25ba96b7acad94e85952d10e7f4cdf010aad2a848d9c620654fe30d6b3f200c492e10e1f0325b3056635568c
6
+ metadata.gz: 8d73669323a7e9cee148d96775e8d9269b2c3c990906ad62e03d20466f93f3f2b7cafb911778fed607f7bdc561eaae57ac35770910de97f893addceb68e122bb
7
+ data.tar.gz: 359fd91c4eb04d4338850de1e38e2162c0d308a2697f86d2ef8ddbf200656384f62e81aa5eabac2c44c5afc79bde0383bc5b672a4a16ad50ab44fa0bcb195323
data/README.md CHANGED
@@ -4,7 +4,9 @@ Ruby's `String` is already a byte sequence. This gem extends it one level lower:
4
4
 
5
5
  The methods are designed for real workloads: Apache Arrow validity bitmaps, bitmap font glyph data, and any protocol buffer where bits carry meaning. Many methods read or modify the existing string bytes directly; methods such as `bit_slice`, `bitwise_not`, `bitwise_and`, `bitwise_or`, `bitwise_xor`, `bits`, `bit_offsets`, `bit_runs`, and `Array#mask` allocate a derived result object. Because the API relies solely on `String`, the same methods would benefit memory-constrained implementations such as mruby and PicoRuby once adopted into CRuby.
6
6
 
7
- A single keyword, `lsb_first:`, controls bit ordering across the API: it selects intra-byte numbering wherever positions are exchanged with the caller, and intra-byte scan direction wherever the API walks the sequence. Across-byte order is always `byte[0]` first, and result Strings from `bit_slice` are always packed LSB-first regardless, so `data.each_bit_offset(true, lsb_first: false).all? { |n| data.bit_at(n, lsb_first: false) }` stays true.
7
+ Every method reads the receiver as a raw byte sequence, so the `String`s that `bit_slice` and the non-destructive `bitwise_*` methods return are always `Encoding::BINARY`. The destructive methods leave the receiver's encoding untouched, as `String#setbyte` does.
8
+
9
+ A single keyword, `lsb_first:`, controls bit ordering across the API: it selects intra-byte numbering wherever positions are exchanged with the caller, and intra-byte scan direction wherever the API walks the sequence. Across-byte order is always `byte[0]` first, and result Strings from `bit_slice` are always packed LSB-first regardless, so `data.each_bit_offset(true, lsb_first: false).all? { |n| data.bit_set?(n, lsb_first: false) }` stays true.
8
10
 
9
11
  ## Usage
10
12
 
@@ -14,7 +16,9 @@ gem install string_bits
14
16
 
15
17
  ```ruby
16
18
  require "string_bits"
17
- puts "\xAA\xAA\xAA\xAA".bit_at(10)
19
+ puts "\xAA\xAA\xAA\xAA".bit_get(10)
20
+ #=> 0
21
+ puts "\xAA\xAA\xAA\xAA".bit_set?(10)
18
22
  #=> false
19
23
 
20
24
  puts "\xAA".each_bit(lsb_first: false).to_a.inspect
@@ -2,10 +2,40 @@
2
2
  #include "ruby/encoding.h"
3
3
 
4
4
  #include <limits.h> /* CHAR_BIT */
5
- #include <stdint.h> /* uint64_t, UINT64_MAX */
5
+ #include <stdint.h> /* uint64_t, UINT64_MAX, int64_t, intptr_t */
6
+ #include <inttypes.h> /* PRIdPTR (ssize_t via intptr_t), PRId64 */
6
7
  #include <string.h> /* memcpy */
7
8
  #include <sys/types.h> /* ssize_t (Ruby typedefs it on Windows) */
8
9
 
10
+ /* Whole-string bit length, computed in 64 bits.
11
+ *
12
+ * RSTRING_LEN returns a pointer-width signed length, so `RSTRING_LEN(s) * 8`
13
+ * overflows a signed 32-bit ssize_t once a string reaches 2**28 bytes (256 MiB)
14
+ * on an ILP32 build, corrupting every bounds check that compares a bit offset
15
+ * against it. Valid bit indices are confined to the Fixnum range and always fit
16
+ * ssize_t, so only this whole-string bit length needs the wider type: computing
17
+ * it in int64_t keeps the bounds checks correct on 32-bit without changing the
18
+ * public pointer-width bit-index contract (see Discussion.md, "Error behavior
19
+ * for out-of-range bit indices").
20
+ *
21
+ * Porting to Ruby Core:
22
+ * 1. Core String lengths are `long` (RSTRING_LEN), which is pointer-width,
23
+ * so `RSTRING_LEN(str) * 8` overflows on ILP32 for strings >= 256 MiB
24
+ * exactly as it does for ssize_t here. Keep the whole-string bit length
25
+ * in a 64-bit intermediate at every bounds check; do not hold it in a
26
+ * `long`. Reuse this macro (or an equivalent inline) rather than open-
27
+ * coding `len * 8`.
28
+ * 2. Keep the public bit-index type pointer-width and keep rejecting
29
+ * out-of-range positions with ArgumentError (see the cross-reference
30
+ * above). Only this internal length is widened, so the contract that
31
+ * core inherits is unchanged.
32
+ * 3. The error-message format specifiers below (<inttypes.h>: (intptr_t)
33
+ * with PRIdPTR for bit offsets, PRId64 for this widened length) exist
34
+ * only because this length is wider than the offsets. In core, follow
35
+ * the local convention for formatting `long` offsets and pick a 64-bit
36
+ * specifier for the widened length accordingly. */
37
+ #define SB_BIT_LEN(byte_len) ((int64_t)(byte_len) * 8)
38
+
9
39
  /* popcount ----------------------------------------------------------------- */
10
40
  /*
11
41
  * Porting to Ruby Core:
@@ -172,22 +202,179 @@ test_bit(const char *ptr, ssize_t bit_index)
172
202
  return rb_str_get_bit(ptr, bit_index, 1);
173
203
  }
174
204
 
175
- /* Convert a Ruby Integer to a ssize_t bit index.
205
+ /* Bit position arguments -------------------------------------------------- */
206
+ /*
207
+ * Every bit offset in this API -- the single-bit offset of bit_get and friends,
208
+ * the offset of an offset/length pair, and each endpoint of a bit Range --
209
+ * follows one contract, the one accepted into Ruby core:
210
+ *
211
+ * - the argument goes through rb_to_int, so any object responding to #to_int
212
+ * is accepted, exactly like the index of String#setbyte;
213
+ * - a negative offset raises IndexError. Negative positions are not read as
214
+ * count-from-end, because combining that normalization with the
215
+ * lsb_first: true/false numbering would make the addressing rule much
216
+ * harder to state (see Discussion.md);
217
+ * - any non-negative offset below 2**64 is a well-formed position. One past
218
+ * the end of the receiver is not an error in itself: it takes the ordinary
219
+ * out-of-range path of whichever method received it (nil, zero, an empty
220
+ * iteration, or IndexError for a mutation);
221
+ * - only an offset that does not fit in uint64_t raises ArgumentError, since
222
+ * no byte buffer on any platform can be addressed by it.
223
+ *
224
+ * The return type is therefore uint64_t. Callers bounds-check it against
225
+ * SB_BIT_LEN(), and any offset that survives that check is below the bit
226
+ * length of a real string, hence always representable as ssize_t.
176
227
  *
177
- * Raises ArgumentError for Bignums on all platforms: a Bignum cannot be a
178
- * valid bit index for any real string, and raising explicitly is clearer than
179
- * silently mapping to a sentinel value that later triggers a different error.
180
228
  * NUM2SSIZET is width-aware (uses FIX2LL on LLP64, FIX2LONG on LP64) so the
181
229
  * FIXNUM extraction does not truncate large FIXNUMs on Windows.
230
+ * RBIGNUM_NEGATIVE_P and rb_absint_size are available via ruby.h.
182
231
  *
183
- * RBIGNUM_NEGATIVE_P is available via ruby.h -> ruby/internal/core/rbignum.h. */
184
- static ssize_t
185
- integer_to_bit_idx(VALUE n)
232
+ * Porting to Ruby Core:
233
+ * Core keeps a `long` fast path alongside the uint64_t value so that the
234
+ * common Fixnum case never leaves the pointer-width arithmetic. That split
235
+ * is a pure optimization; the observable contract is the one above.
236
+ */
237
+
238
+ /* True when a positive Integer does not fit uint64_t. rb_absint_size returns
239
+ * the byte width of the absolute value, so the bound is exactly 8 bytes. */
240
+ static inline int
241
+ sb_exceeds_uint64(VALUE integer)
242
+ {
243
+ return rb_absint_size(integer, NULL) > sizeof(uint64_t);
244
+ }
245
+
246
+ static uint64_t
247
+ sb_bit_offset(VALUE n)
186
248
  {
187
- if (FIXNUM_P(n)) return NUM2SSIZET(n);
249
+ VALUE integer = rb_to_int(n);
250
+
251
+ if (FIXNUM_P(integer)) {
252
+ ssize_t value = NUM2SSIZET(integer);
253
+ if (value < 0) {
254
+ rb_raise(rb_eIndexError, "bit index out of range");
255
+ }
256
+ return (uint64_t)value;
257
+ }
258
+
259
+ RUBY_ASSERT(RB_TYPE_P(integer, T_BIGNUM));
260
+ if (RBIGNUM_NEGATIVE_P(integer)) {
261
+ rb_raise(rb_eIndexError, "bit index out of range");
262
+ }
263
+ if (sb_exceeds_uint64(integer)) {
264
+ rb_raise(rb_eArgError, "bit index out of representable range");
265
+ }
266
+ return (uint64_t)NUM2ULL(integer);
267
+ }
268
+
269
+ /* Bit lengths share the offset's representable range, but a negative length is
270
+ * an ArgumentError rather than an IndexError: it is a malformed size, not a
271
+ * position outside the string. A length that runs past the end of the receiver
272
+ * is clamped by the caller, matching byteslice. */
273
+ static uint64_t
274
+ sb_bit_length(VALUE n)
275
+ {
276
+ VALUE integer = rb_to_int(n);
277
+
278
+ if (FIXNUM_P(integer)) {
279
+ ssize_t value = NUM2SSIZET(integer);
280
+ if (value < 0) {
281
+ rb_raise(rb_eArgError, "bit_length must be non-negative");
282
+ }
283
+ return (uint64_t)value;
284
+ }
285
+
286
+ RUBY_ASSERT(RB_TYPE_P(integer, T_BIGNUM));
287
+ if (RBIGNUM_NEGATIVE_P(integer)) {
288
+ rb_raise(rb_eArgError, "bit_length must be non-negative");
289
+ }
290
+ if (sb_exceeds_uint64(integer)) {
291
+ rb_raise(rb_eArgError, "bit_length out of representable range");
292
+ }
293
+ return (uint64_t)NUM2ULL(integer);
294
+ }
295
+
296
+ /* Soft variants for bit_slice, which answers nil for an argument it cannot
297
+ * use instead of raising -- the same leniency String#byteslice has. Only the
298
+ * unrepresentable case still raises, so the boundary between "a position past
299
+ * the end" and "not a position at all" stays where the rest of the API puts
300
+ * it. Returns 0 on success, or -1 for a non-Integer or a negative value. */
301
+ static int
302
+ sb_bit_position_soft(VALUE n, uint64_t *out)
303
+ {
304
+ if (!rb_integer_type_p(n)) return -1;
305
+
306
+ if (FIXNUM_P(n)) {
307
+ ssize_t value = NUM2SSIZET(n);
308
+ if (value < 0) return -1;
309
+ *out = (uint64_t)value;
310
+ return 0;
311
+ }
312
+
188
313
  RUBY_ASSERT(RB_TYPE_P(n, T_BIGNUM));
189
- rb_raise(rb_eArgError, "bit index out of representable range");
190
- UNREACHABLE_RETURN(0);
314
+ if (RBIGNUM_NEGATIVE_P(n)) return -1;
315
+ if (sb_exceeds_uint64(n)) {
316
+ rb_raise(rb_eArgError, "bit index out of representable range");
317
+ }
318
+ *out = (uint64_t)NUM2ULL(n);
319
+ return 0;
320
+ }
321
+
322
+ /* Narrowing a parsed position to the internal index ----------------------- */
323
+ /*
324
+ * Positions are parsed in 64 bits but held in a pointer-width signed integer
325
+ * once they address memory, and on an ILP32 build those two widths differ:
326
+ * SB_BIT_LEN can reach 2**34 while ssize_t stops at 2**31-1, so a string of
327
+ * 256 MiB or more has bits that exist but cannot be indexed internally. Such a
328
+ * position gets the same answer as one that does not fit 64 bits at all --
329
+ * ArgumentError -- rather than being silently truncated into a valid-looking
330
+ * index. On LP64 the gap is empty and none of this is reachable: no String is
331
+ * large enough for its bit length to exceed SSIZE_MAX.
332
+ */
333
+ #ifndef SSIZE_MAX
334
+ # define SSIZE_MAX ((ssize_t)(((size_t)-1) >> 1))
335
+ #endif
336
+ #define SB_MAX_BIT_POS ((uint64_t)SSIZE_MAX)
337
+
338
+ /* The receiver's bit length as an internal index, saturated at ssize_t. */
339
+ static inline ssize_t
340
+ sb_addressable_bits(int64_t total)
341
+ {
342
+ return (ssize_t)(((uint64_t)total > SB_MAX_BIT_POS) ? SB_MAX_BIT_POS : (uint64_t)total);
343
+ }
344
+
345
+ /* Narrow a parsed position for the methods whose answer at or past the end is
346
+ * "there is nothing here" (an empty iteration, a count of zero, an empty
347
+ * slice): those saturate at the end of the string rather than failing. */
348
+ static ssize_t
349
+ sb_narrow_bit_pos(uint64_t pos, int64_t total)
350
+ {
351
+ if (pos >= (uint64_t)total) return sb_addressable_bits(total);
352
+ if (pos > SB_MAX_BIT_POS) {
353
+ rb_raise(rb_eArgError, "bit index out of representable range");
354
+ }
355
+ return (ssize_t)pos;
356
+ }
357
+
358
+ /* Clamp a parsed length to the bits still addressable after `beg`. */
359
+ static ssize_t
360
+ sb_clamp_bit_length(uint64_t len, ssize_t beg, int64_t total)
361
+ {
362
+ uint64_t available = (uint64_t)sb_addressable_bits(total) - (uint64_t)beg;
363
+ return (ssize_t)(len > available ? available : len);
364
+ }
365
+
366
+ /* Resolve a single-bit offset against the receiver's bit length.
367
+ * Returns -1 when the offset is past the end of the string. */
368
+ static ssize_t
369
+ sb_resolve_bit_offset(VALUE self, VALUE n)
370
+ {
371
+ int64_t total = SB_BIT_LEN(RSTRING_LEN(self));
372
+ uint64_t offset = sb_bit_offset(n);
373
+ if ((uint64_t)total <= offset) return -1;
374
+ if (offset > SB_MAX_BIT_POS) {
375
+ rb_raise(rb_eArgError, "bit index out of representable range");
376
+ }
377
+ return (ssize_t)offset;
191
378
  }
192
379
 
193
380
  /* Bit numbering between byte-with-LSB-as-bit-0 and byte-with-MSB-as-bit-0
@@ -223,54 +410,32 @@ logical_write_bit(unsigned char *ptr, ssize_t logical_index, int lsb_first, int
223
410
  static ssize_t
224
411
  check_bit_index(VALUE self, VALUE n, int lsb_first)
225
412
  {
226
- if (!rb_integer_type_p(n)) {
227
- rb_raise(rb_eTypeError, "bit index must be an integer");
228
- }
229
- ssize_t idx = integer_to_bit_idx(n);
230
- ssize_t size = RSTRING_LEN(self) * 8;
231
- if (idx < 0 || idx >= size) {
413
+ ssize_t idx = sb_resolve_bit_offset(self, n);
414
+ if (idx < 0) {
232
415
  rb_raise(rb_eIndexError, "bit index out of range");
233
416
  }
234
417
  return logical_to_physical(idx, lsb_first);
235
418
  }
236
419
 
237
- /* ssize_t-interface wrapper around rb_range_beg_len.
420
+ /* Resolve a bit Range against the receiver's bit length.
238
421
  *
239
- * rb_range_beg_len() takes (long *begp, long *lenp, long len), but this
240
- * extension uses ssize_t throughout for LP64/LLP64 uniformity. On Windows
241
- * (LLP64) long is 32-bit while ssize_t is 64-bit, so passing &ssize_t to the
242
- * stock API is a type error and the build fails. This wrapper bridges the
243
- * two and clamps the input length to LONG_MAX on platforms where ssize_t
244
- * is wider than long, which has no practical effect: a 2 GiB string is
245
- * already past what any realistic caller will hand us.
422
+ * This replaces rb_range_beg_len() rather than wrapping it, for two reasons.
423
+ * rb_range_beg_len() takes `long *` endpoints, so on Windows (LLP64), where
424
+ * long is 32-bit while ssize_t is 64-bit, its interface does not match the
425
+ * pointer-width bit index used here. More importantly, it raises RangeError
426
+ * for any endpoint that does not fit `long`, whereas a bit Range endpoint is
427
+ * allowed to be any position up to UINT64_MAX (see sb_bit_offset); such an
428
+ * endpoint simply lies past the end of the string.
246
429
  *
247
- * It also catches the RangeError that rb_range_beg_len raises when a Range
248
- * endpoint is a Bignum too large for `long`, and re-raises it as IndexError
249
- * so that out-of-range bit positions report uniformly across LP64 and LLP64.
250
- */
251
- struct sb_range_args {
252
- VALUE range;
253
- long *lbegp;
254
- long *llenp;
255
- long len;
256
- int err;
257
- };
258
-
259
- static VALUE
260
- sb_range_beg_len_call(VALUE arg)
261
- {
262
- struct sb_range_args *a = (struct sb_range_args *)arg;
263
- return rb_range_beg_len(a->range, a->lbegp, a->llenp, a->len, a->err);
264
- }
265
-
266
- /* Validate Range endpoints for bit position arguments.
267
- * Raises ArgumentError for:
268
- * - any explicit (non-nil) Bignum endpoint: cannot address any real string,
269
- * consistent with integer_to_bit_idx behavior for scalar indices.
270
- * - any explicit (non-nil) negative endpoint: count-from-end semantics
271
- * interact confusingly with lsb_first: true/false.
272
- * RBIGNUM_NEGATIVE_P is used for the negativity check on Bignums to avoid
273
- * calling NUM2LL on values that do not fit in long long.
430
+ * The resolution rules match rb_range_beg_len() with err=0, minus the
431
+ * count-from-end normalization that this API does not have:
432
+ * - a nil begin is 0, a nil end runs to the end of the string;
433
+ * - an end past the end of the string is clamped to it;
434
+ * - a begin past the end of the string reports SB_RANGE_OUT, and each caller
435
+ * decides what that means (0 bits counted, a nil slice, an IndexError).
436
+ * Like rb_range_beg_len(), the returned length may exceed the bits actually
437
+ * available when the end was clamped, so callers that write must re-check
438
+ * beg + len against the total (see rb_str_mutate_bits and rb_str_bit_splice).
274
439
  *
275
440
  * Porting to Ruby Core:
276
441
  * Replace rb_range_values() with direct struct access:
@@ -279,47 +444,39 @@ sb_range_beg_len_call(VALUE arg)
279
444
  * end = RANGE_END(range);
280
445
  * excl = RANGE_EXCL(range);
281
446
  */
282
- static void
283
- sb_range_validate_endpoints(VALUE range)
447
+ enum sb_range_result {
448
+ SB_RANGE_OUT = 0, /* the range begins past the end of the string */
449
+ SB_RANGE_OK = 1
450
+ };
451
+
452
+ static enum sb_range_result
453
+ sb_bit_range_beg_len(VALUE range, ssize_t *begp, ssize_t *lenp, int64_t total)
284
454
  {
285
- VALUE beg, end;
455
+ VALUE beg_v, end_v;
286
456
  int excl;
287
- rb_range_values(range, &beg, &end, &excl);
288
- if (!NIL_P(beg) && rb_integer_type_p(beg)) {
289
- if (!FIXNUM_P(beg))
290
- rb_raise(rb_eArgError, "bit index out of representable range");
291
- if (FIX2LONG(beg) < 0)
292
- rb_raise(rb_eIndexError,
293
- "negative Range endpoint is not allowed for bit positions");
294
- }
295
- if (!NIL_P(end) && rb_integer_type_p(end)) {
296
- if (!FIXNUM_P(end))
297
- rb_raise(rb_eArgError, "bit index out of representable range");
298
- if (FIX2LONG(end) < 0)
299
- rb_raise(rb_eIndexError,
300
- "negative Range endpoint is not allowed for bit positions");
301
- }
302
- }
457
+ rb_range_values(range, &beg_v, &end_v, &excl);
303
458
 
304
- static inline VALUE
305
- sb_range_beg_len(VALUE range, ssize_t *begp, ssize_t *lenp, ssize_t len, int err)
306
- {
307
- long lbeg = 0, llen = 0;
308
- long clipped = (len > (ssize_t)LONG_MAX) ? LONG_MAX : (long)len;
309
- struct sb_range_args args = { range, &lbeg, &llen, clipped, err };
310
- int state = 0;
311
- VALUE result = rb_protect(sb_range_beg_len_call, (VALUE)&args, &state);
312
- if (state) {
313
- VALUE exc = rb_errinfo();
314
- rb_set_errinfo(Qnil);
315
- if (rb_obj_is_kind_of(exc, rb_eRangeError)) {
316
- rb_raise(rb_eIndexError, "bit range out of range");
317
- }
318
- rb_exc_raise(exc);
459
+ uint64_t total_u = (uint64_t)total;
460
+ uint64_t beg = NIL_P(beg_v) ? 0 : sb_bit_offset(beg_v);
461
+ if (beg > total_u) return SB_RANGE_OUT;
462
+
463
+ uint64_t end_exclusive;
464
+ if (NIL_P(end_v)) {
465
+ end_exclusive = total_u;
319
466
  }
320
- if (begp) *begp = (ssize_t)lbeg;
321
- if (lenp) *lenp = (ssize_t)llen;
322
- return result;
467
+ else {
468
+ uint64_t end = sb_bit_offset(end_v);
469
+ if (end > total_u) end = total_u;
470
+ end_exclusive = excl ? end : end + 1;
471
+ }
472
+
473
+ /* The length is deliberately left unclamped: when the end was clamped it
474
+ * can exceed the bits available, which is exactly what lets a writing
475
+ * caller detect the overrun. Reading callers clamp it themselves. */
476
+ uint64_t len = (end_exclusive > beg) ? end_exclusive - beg : 0;
477
+ *begp = sb_narrow_bit_pos(beg, total);
478
+ *lenp = (ssize_t)(len > SB_MAX_BIT_POS ? SB_MAX_BIT_POS : len);
479
+ return SB_RANGE_OK;
323
480
  }
324
481
 
325
482
  static void
@@ -342,16 +499,20 @@ validate_option_hash(VALUE opts, unsigned allowed)
342
499
  }
343
500
  }
344
501
 
502
+ /* An absent keyword falls back to the default, but an explicitly passed value
503
+ * must be true or false: `lsb_first: nil` is a caller mistake, not a request
504
+ * for the default, and silently treating it as LSB-first would flip the bit
505
+ * numbering of a getter and its matching setter apart. */
345
506
  static int
346
507
  parse_bool_opt(VALUE opts, VALUE key, const char *name, int default_value)
347
508
  {
348
509
  if (NIL_P(opts)) return default_value;
349
- VALUE value = rb_hash_aref(opts, key);
350
- if (NIL_P(value)) return default_value;
510
+ VALUE value = rb_hash_lookup2(opts, key, Qundef);
511
+ if (value == Qundef) return default_value;
351
512
  if (value == Qtrue) return 1;
352
513
  if (value == Qfalse) return 0;
353
514
  rb_raise(rb_eArgError, "%s must be true or false", name);
354
- return default_value;
515
+ UNREACHABLE_RETURN(default_value);
355
516
  }
356
517
 
357
518
  static int
@@ -360,48 +521,49 @@ parse_lsb_first_opt(VALUE opts)
360
521
  return parse_bool_opt(opts, sym_lsb_first, "lsb_first", 1);
361
522
  }
362
523
 
363
- /* Parse an optional start_offset positional argument (Qnil => 0).
364
- * Raises ArgumentError for Bignum, IndexError for negative Fixnum. */
524
+ /* Parse the optional start_offset positional argument of the iterators
525
+ * (Qnil => 0). A start past the end of the receiver is clamped to its bit
526
+ * length, where every emitter already stops immediately. */
365
527
  static ssize_t
366
- parse_start_offset(VALUE v)
528
+ parse_start_offset(VALUE self, VALUE v)
367
529
  {
368
530
  if (NIL_P(v)) return 0;
369
- ssize_t start_offset = integer_to_bit_idx(v); /* raises ArgumentError for Bignum */
370
- if (start_offset < 0)
371
- rb_raise(rb_eIndexError, "bit_offset must be non-negative");
372
- return start_offset;
531
+ return sb_narrow_bit_pos(sb_bit_offset(v), SB_BIT_LEN(RSTRING_LEN(self)));
373
532
  }
374
533
 
375
534
  /* read -------------------------------------------------------------------- */
376
535
 
377
- /* Return true/false/nil for the bit at flat position n. */
378
- static VALUE
379
- rb_str_bit_at(int argc, VALUE *argv, VALUE self)
536
+ /* Shared body of bit_get and bit_set?.
537
+ * Returns the bit value (0 or 1), or -1 when the offset is out of range. */
538
+ static int
539
+ str_bit_read(int argc, VALUE *argv, VALUE self)
380
540
  {
381
541
  VALUE bit_offset_v, opts;
382
542
  rb_scan_args(argc, argv, "1:", &bit_offset_v, &opts);
383
543
  validate_option_hash(opts, SB_KW_LSB_FIRST);
544
+ int lsb_first = parse_lsb_first_opt(opts);
384
545
 
385
- if (!rb_integer_type_p(bit_offset_v)) {
386
- rb_raise(rb_eTypeError, "bit index must be an integer");
387
- }
388
- ssize_t bit_offset = integer_to_bit_idx(bit_offset_v);
389
- if (bit_offset < 0) {
390
- rb_raise(rb_eIndexError, "bit index out of range");
391
- }
392
- ssize_t size = RSTRING_LEN(self) * 8;
393
- if (size <= bit_offset) {
394
- return Qnil;
395
- }
546
+ ssize_t bit_offset = sb_resolve_bit_offset(self, bit_offset_v);
547
+ if (bit_offset < 0) return -1;
396
548
 
397
- int lsb_first = parse_lsb_first_opt(opts);
398
- ssize_t idx = logical_to_physical(bit_offset, lsb_first);
549
+ return test_bit(RSTRING_PTR(self), logical_to_physical(bit_offset, lsb_first));
550
+ }
399
551
 
400
- if (test_bit(RSTRING_PTR(self), idx)) {
401
- return Qtrue;
402
- } else {
403
- return Qfalse;
404
- }
552
+ /* Return 1/0/nil for the bit at flat position n. */
553
+ static VALUE
554
+ rb_str_bit_get(int argc, VALUE *argv, VALUE self)
555
+ {
556
+ int bit = str_bit_read(argc, argv, self);
557
+ return bit < 0 ? Qnil : INT2FIX(bit);
558
+ }
559
+
560
+ /* Return true/false/nil for the bit at flat position n. */
561
+ static VALUE
562
+ rb_str_bit_set_p(int argc, VALUE *argv, VALUE self)
563
+ {
564
+ int bit = str_bit_read(argc, argv, self);
565
+ if (bit < 0) return Qnil;
566
+ return bit ? Qtrue : Qfalse;
405
567
  }
406
568
 
407
569
  /* count_set_bits: popcount over a raw byte buffer.
@@ -457,9 +619,9 @@ count_set_bits_range(const unsigned char *str, ssize_t total_bytes,
457
619
  ssize_t start, ssize_t length)
458
620
  {
459
621
  if (length <= 0) return 0;
460
- ssize_t total_bits = total_bytes * 8;
622
+ int64_t total_bits = SB_BIT_LEN(total_bytes);
461
623
  if (start >= total_bits) return 0;
462
- if (start + length > total_bits) length = total_bits - start;
624
+ if (start + length > total_bits) length = (ssize_t)(total_bits - start);
463
625
 
464
626
  ssize_t byte_start = start >> 3;
465
627
  int bit_lo = (int)(start & 7);
@@ -494,9 +656,9 @@ count_set_bits_range_msb(const unsigned char *str, ssize_t total_bytes,
494
656
  ssize_t start, ssize_t length)
495
657
  {
496
658
  if (length <= 0) return 0;
497
- ssize_t total_bits = total_bytes * 8;
659
+ int64_t total_bits = SB_BIT_LEN(total_bytes);
498
660
  if (start >= total_bits) return 0;
499
- if (start + length > total_bits) length = total_bits - start;
661
+ if (start + length > total_bits) length = (ssize_t)(total_bits - start);
500
662
 
501
663
  ssize_t byte_start = start >> 3;
502
664
  int s_bit = (int)(start & 7); /* MSB-first within-byte start index */
@@ -543,30 +705,26 @@ rb_str_bit_count(int argc, VALUE *argv, VALUE self)
543
705
  return SSIZET2NUM(count_set_bits(str, src_len));
544
706
 
545
707
  int lsb_first = parse_lsb_first_opt(opts);
546
- ssize_t total_bits = src_len * 8;
708
+ int64_t total_bits = SB_BIT_LEN(src_len);
547
709
  ssize_t bit_offset, bit_length;
548
710
 
549
711
  if (rb_obj_is_kind_of(v0, rb_cRange)) {
550
712
  if (!NIL_P(v1))
551
713
  rb_raise(rb_eArgError, "wrong number of arguments");
552
- sb_range_validate_endpoints(v0);
553
714
  ssize_t beg, len;
554
- if (!RTEST(sb_range_beg_len(v0, &beg, &len, total_bits, 0)))
715
+ if (sb_bit_range_beg_len(v0, &beg, &len, total_bits) == SB_RANGE_OUT)
555
716
  return INT2FIX(0);
556
717
  bit_offset = beg;
557
718
  bit_length = len;
558
719
  }
559
720
  else if (!NIL_P(v1)) {
560
- if (!rb_integer_type_p(v0))
561
- rb_raise(rb_eTypeError, "bit_offset must be an integer");
562
- if (!rb_integer_type_p(v1))
563
- rb_raise(rb_eTypeError, "bit_length must be an integer");
564
- bit_offset = integer_to_bit_idx(v0);
565
- if (bit_offset < 0)
566
- rb_raise(rb_eIndexError, "bit_offset must be non-negative");
567
- bit_length = integer_to_bit_idx(v1);
568
- if (bit_length < 0)
569
- rb_raise(rb_eArgError, "bit_length must be non-negative");
721
+ uint64_t off = sb_bit_offset(v0);
722
+ uint64_t len = sb_bit_length(v1);
723
+ /* A region starting at or past the end holds no bits, and one that
724
+ * runs off the end contributes only the part that exists. */
725
+ if (off >= (uint64_t)total_bits) return INT2FIX(0);
726
+ bit_offset = sb_narrow_bit_pos(off, total_bits);
727
+ bit_length = sb_clamp_bit_length(len, bit_offset, total_bits);
570
728
  }
571
729
  else {
572
730
  rb_raise(rb_eArgError,
@@ -590,7 +748,7 @@ rb_str_bit_count(int argc, VALUE *argv, VALUE self)
590
748
  static void
591
749
  emit_bits(const unsigned char *str, ssize_t len, int lsb_first, ssize_t start_offset, VALUE ary)
592
750
  {
593
- if (start_offset >= len * 8) return;
751
+ if (start_offset >= SB_BIT_LEN(len)) return;
594
752
 
595
753
  #define SB_EMIT(v) \
596
754
  do { VALUE _b = (v); \
@@ -629,7 +787,7 @@ rb_str_each_bit(int argc, VALUE *argv, VALUE self)
629
787
  rb_scan_args(argc, argv, "01:", &start_offset_v, &opts);
630
788
  validate_option_hash(opts, SB_KW_LSB_FIRST);
631
789
  int lsb_first = parse_lsb_first_opt(opts);
632
- ssize_t start_offset = parse_start_offset(start_offset_v);
790
+ ssize_t start_offset = parse_start_offset(self, start_offset_v);
633
791
 
634
792
  emit_bits((const unsigned char *)RSTRING_PTR(self), RSTRING_LEN(self),
635
793
  lsb_first, start_offset, Qnil);
@@ -643,7 +801,7 @@ rb_str_bits(int argc, VALUE *argv, VALUE self)
643
801
  rb_scan_args(argc, argv, "01:", &start_offset_v, &opts);
644
802
  validate_option_hash(opts, SB_KW_LSB_FIRST);
645
803
  int lsb_first = parse_lsb_first_opt(opts);
646
- ssize_t start_offset = parse_start_offset(start_offset_v);
804
+ ssize_t start_offset = parse_start_offset(self, start_offset_v);
647
805
  ssize_t len = RSTRING_LEN(self);
648
806
  const unsigned char *str = (const unsigned char *)RSTRING_PTR(self);
649
807
 
@@ -652,7 +810,8 @@ rb_str_bits(int argc, VALUE *argv, VALUE self)
652
810
  return self;
653
811
  }
654
812
 
655
- ssize_t nbits = (start_offset >= len * 8) ? 0 : (len * 8 - start_offset);
813
+ int64_t total_bits = SB_BIT_LEN(len);
814
+ ssize_t nbits = (start_offset >= total_bits) ? 0 : (ssize_t)(total_bits - start_offset);
656
815
  VALUE ary = rb_ary_new_capa(nbits);
657
816
  emit_bits(str, len, lsb_first, start_offset, ary);
658
817
  return ary;
@@ -690,7 +849,7 @@ static void
690
849
  emit_bit_offsets(const unsigned char *str, ssize_t len, int target, int lsb_first,
691
850
  ssize_t start_offset, VALUE ary)
692
851
  {
693
- if (start_offset >= len * 8) return;
852
+ if (start_offset >= SB_BIT_LEN(len)) return;
694
853
 
695
854
  #define SB_EMIT(pos_val) \
696
855
  do { VALUE _p = (pos_val); \
@@ -775,7 +934,7 @@ rb_str_each_bit_offset(int argc, VALUE *argv, VALUE self)
775
934
  validate_option_hash(opts, SB_KW_LSB_FIRST);
776
935
  int lsb_first = parse_lsb_first_opt(opts);
777
936
  int target = parse_bit_target(bit_val);
778
- ssize_t start_offset = parse_start_offset(start_offset_v);
937
+ ssize_t start_offset = parse_start_offset(self, start_offset_v);
779
938
 
780
939
  emit_bit_offsets((const unsigned char *)RSTRING_PTR(self), RSTRING_LEN(self),
781
940
  target, lsb_first, start_offset, Qnil);
@@ -790,7 +949,7 @@ rb_str_bit_offsets(int argc, VALUE *argv, VALUE self)
790
949
  validate_option_hash(opts, SB_KW_LSB_FIRST);
791
950
  int lsb_first = parse_lsb_first_opt(opts);
792
951
  int target = parse_bit_target(bit_val);
793
- ssize_t start_offset = parse_start_offset(start_offset_v);
952
+ ssize_t start_offset = parse_start_offset(self, start_offset_v);
794
953
 
795
954
  ssize_t len = RSTRING_LEN(self);
796
955
  const unsigned char *str = (const unsigned char *)RSTRING_PTR(self);
@@ -803,7 +962,7 @@ rb_str_bit_offsets(int argc, VALUE *argv, VALUE self)
803
962
  /* Pre-size the Array using popcount to avoid repeated reallocation.
804
963
  * For target=0 the expected count is (len * 8 - popcount). */
805
964
  ssize_t set_count = count_set_bits(str, len);
806
- ssize_t count = (target == 1) ? set_count : (len * 8 - set_count);
965
+ ssize_t count = (target == 1) ? set_count : (ssize_t)(SB_BIT_LEN(len) - set_count);
807
966
  VALUE ary = rb_ary_new_capa(count);
808
967
  emit_bit_offsets(str, len, target, lsb_first, start_offset, ary);
809
968
  return ary;
@@ -908,7 +1067,7 @@ static VALUE
908
1067
  rb_str_bit_slice(int argc, VALUE *argv, VALUE self)
909
1068
  {
910
1069
  ssize_t src_len = RSTRING_LEN(self);
911
- ssize_t total_bits = src_len * 8;
1070
+ int64_t total_bits = SB_BIT_LEN(src_len);
912
1071
  ssize_t bit_offset, bit_length;
913
1072
  VALUE v0, v1, opts;
914
1073
  int n_pos = rb_scan_args(argc, argv, "11:", &v0, &v1, &opts);
@@ -916,23 +1075,21 @@ rb_str_bit_slice(int argc, VALUE *argv, VALUE self)
916
1075
  int lsb_first = parse_lsb_first_opt(opts);
917
1076
 
918
1077
  if (n_pos == 1 && rb_obj_is_kind_of(v0, rb_cRange)) {
919
- sb_range_validate_endpoints(v0);
920
1078
  ssize_t beg, len;
921
- if (!RTEST(sb_range_beg_len(v0, &beg, &len, total_bits, 0))) {
1079
+ if (sb_bit_range_beg_len(v0, &beg, &len, total_bits) == SB_RANGE_OUT) {
922
1080
  return Qnil;
923
1081
  }
924
1082
  bit_offset = beg;
925
1083
  bit_length = len;
926
1084
  }
927
1085
  else if (n_pos == 2) {
928
- if (!rb_integer_type_p(v0) || !rb_integer_type_p(v1)) {
929
- return Qnil;
930
- }
1086
+ uint64_t off, len;
1087
+ if (sb_bit_position_soft(v0, &off) < 0) return Qnil;
1088
+ if (sb_bit_position_soft(v1, &len) < 0) return Qnil;
1089
+ if (off > (uint64_t)total_bits) return Qnil;
931
1090
 
932
- bit_offset = integer_to_bit_idx(v0);
933
- bit_length = integer_to_bit_idx(v1);
934
-
935
- if (bit_offset < 0 || bit_length < 0) return Qnil;
1091
+ bit_offset = sb_narrow_bit_pos(off, total_bits);
1092
+ bit_length = sb_clamp_bit_length(len, bit_offset, total_bits);
936
1093
  }
937
1094
  else if (n_pos == 1) {
938
1095
  return Qnil;
@@ -943,15 +1100,18 @@ rb_str_bit_slice(int argc, VALUE *argv, VALUE self)
943
1100
  }
944
1101
 
945
1102
  if (bit_offset > total_bits) return Qnil;
946
- ssize_t available = total_bits - bit_offset;
947
- if (bit_length > available) bit_length = available;
1103
+ int64_t available = total_bits - bit_offset;
1104
+ if (bit_length > available) bit_length = (ssize_t)available;
948
1105
 
949
1106
  if (bit_length == 0) return rb_str_new("", 0);
950
1107
 
951
1108
  ssize_t out_bytes = (bit_length + 7) / 8;
952
1109
  VALUE result = rb_str_buf_new(out_bytes);
953
1110
  rb_str_resize(result, out_bytes);
954
- rb_enc_associate(result, rb_enc_get(self));
1111
+ /* A bit-aligned slice is a repacked byte sequence, not a substring, so the
1112
+ * result carries BINARY like every other String returned by this API. */
1113
+ rb_enc_associate(result, rb_ascii8bit_encoding());
1114
+ ENC_CODERANGE_CLEAR(result);
955
1115
  unsigned char *dst = (unsigned char *)RSTRING_PTR(result);
956
1116
  const unsigned char *src = (const unsigned char *)RSTRING_PTR(self);
957
1117
 
@@ -997,88 +1157,68 @@ rb_str_mutate_bits(int argc, VALUE *argv, VALUE self, enum sb_mutation_op op)
997
1157
  validate_option_hash(opts, SB_KW_LSB_FIRST);
998
1158
  int lsb_first = parse_lsb_first_opt(opts);
999
1159
 
1000
- rb_str_modify(self);
1001
- unsigned char *ptr = (unsigned char *)RSTRING_PTR(self);
1002
-
1003
- if (rb_integer_type_p(target)) {
1004
- if (NIL_P(bit_length_v)) {
1005
- /* Single-bit form: bit_set(n) */
1006
- ssize_t idx = check_bit_index(self, target, lsb_first);
1007
- unsigned char mask = (unsigned char)(1u << (idx % 8));
1008
- switch (op) {
1009
- case SB_MUT_SET: ptr[idx / 8] |= mask; break;
1010
- case SB_MUT_CLEAR: ptr[idx / 8] &= (unsigned char)~mask; break;
1011
- case SB_MUT_FLIP: ptr[idx / 8] ^= mask; break;
1012
- }
1013
- return self;
1014
- }
1015
- /* 2-arg form: bit_set(bit_offset, bit_length) */
1016
- if (!rb_integer_type_p(bit_length_v))
1017
- rb_raise(rb_eTypeError, "bit_length must be an integer");
1018
- ssize_t bit_offset = integer_to_bit_idx(target);
1019
- if (bit_offset < 0)
1020
- rb_raise(rb_eIndexError, "bit_offset must be non-negative");
1021
- ssize_t bit_length = integer_to_bit_idx(bit_length_v);
1022
- if (bit_length < 0)
1023
- rb_raise(rb_eArgError, "bit_length must be non-negative");
1024
- if (bit_length == 0) return self;
1025
- ssize_t total_bits = RSTRING_LEN(self) * 8;
1026
- if (bit_offset >= total_bits || bit_offset + bit_length > total_bits)
1027
- rb_raise(rb_eIndexError, "bit range out of range");
1028
- for (ssize_t logical = bit_offset; logical < bit_offset + bit_length; logical++) {
1029
- ssize_t idx = logical_to_physical(logical, lsb_first);
1030
- unsigned char mask = (unsigned char)(1u << (idx % 8));
1031
- switch (op) {
1032
- case SB_MUT_SET: ptr[idx / 8] |= mask; break;
1033
- case SB_MUT_CLEAR: ptr[idx / 8] &= (unsigned char)~mask; break;
1034
- case SB_MUT_FLIP: ptr[idx / 8] ^= mask; break;
1035
- }
1160
+ unsigned char *ptr;
1161
+
1162
+ /* Single-bit form: bit_set(n). The offset is validated before
1163
+ * rb_str_modify, so an out-of-range offset raises IndexError even on a
1164
+ * frozen receiver, matching the order of the core implementation. */
1165
+ if (NIL_P(bit_length_v) && !rb_obj_is_kind_of(target, rb_cRange)) {
1166
+ ssize_t idx = check_bit_index(self, target, lsb_first);
1167
+ rb_str_modify(self);
1168
+ ptr = (unsigned char *)RSTRING_PTR(self);
1169
+ unsigned char mask = (unsigned char)(1u << (idx % 8));
1170
+ switch (op) {
1171
+ case SB_MUT_SET: ptr[idx / 8] |= mask; break;
1172
+ case SB_MUT_CLEAR: ptr[idx / 8] &= (unsigned char)~mask; break;
1173
+ case SB_MUT_FLIP: ptr[idx / 8] ^= mask; break;
1036
1174
  }
1037
1175
  return self;
1038
1176
  }
1039
1177
 
1040
- if (!NIL_P(bit_length_v))
1041
- rb_raise(rb_eArgError, "wrong number of arguments");
1178
+ int64_t total_bits = SB_BIT_LEN(RSTRING_LEN(self));
1179
+ ssize_t bit_offset, bit_length;
1042
1180
 
1043
1181
  if (rb_obj_is_kind_of(target, rb_cRange)) {
1044
- sb_range_validate_endpoints(target);
1045
- ssize_t total_bits = RSTRING_LEN(self) * 8;
1046
- ssize_t beg, len;
1182
+ if (!NIL_P(bit_length_v))
1183
+ rb_raise(rb_eArgError, "wrong number of arguments");
1047
1184
 
1048
- /* err=0 returns Qnil for out-of-range begin (after negative normalization);
1049
- * convert that to IndexError to stay consistent with single-bit access. */
1050
- if (!RTEST(sb_range_beg_len(target, &beg, &len, total_bits, 0))) {
1185
+ /* A range that begins past the end has no bits to write, which is an
1186
+ * error for a mutation, not a silent no-op. */
1187
+ if (sb_bit_range_beg_len(target, &bit_offset, &bit_length, total_bits) == SB_RANGE_OUT)
1051
1188
  rb_raise(rb_eIndexError, "bit range out of range");
1052
- }
1053
1189
 
1054
- /* err=0 silently clamps end > total. Detect that and raise instead,
1055
- * to stay consistent with bit_splice and single-bit mutation. */
1056
- VALUE rng_beg_unused, rng_end_v;
1057
- int excl;
1058
- rb_range_values(target, &rng_beg_unused, &rng_end_v, &excl);
1059
- (void)rng_beg_unused;
1060
- if (!NIL_P(rng_end_v)) {
1061
- ssize_t end_val = integer_to_bit_idx(rng_end_v);
1062
- ssize_t end_excl = excl ? end_val : end_val + 1;
1063
- if (end_excl > total_bits) {
1064
- rb_raise(rb_eIndexError, "bit range out of range");
1065
- }
1066
- }
1190
+ /* sb_bit_range_beg_len clamps the end, so a range that overruns the
1191
+ * string comes back longer than the bits available. Writing is not
1192
+ * allowed to silently shrink, so report the overrun instead. */
1193
+ if ((int64_t)bit_offset + (int64_t)bit_length > total_bits)
1194
+ rb_raise(rb_eIndexError, "bit range out of range");
1195
+ }
1196
+ else {
1197
+ /* 2-arg form: bit_set(bit_offset, bit_length) */
1198
+ uint64_t off = sb_bit_offset(target);
1199
+ uint64_t len = sb_bit_length(bit_length_v);
1200
+ if (len == 0) return self;
1201
+ if (off >= (uint64_t)total_bits || len > (uint64_t)total_bits - off)
1202
+ rb_raise(rb_eIndexError, "bit range out of range");
1203
+ bit_offset = sb_narrow_bit_pos(off, total_bits);
1204
+ if (len > (uint64_t)(sb_addressable_bits(total_bits) - bit_offset))
1205
+ rb_raise(rb_eArgError, "bit index out of representable range");
1206
+ bit_length = (ssize_t)len;
1207
+ }
1067
1208
 
1068
- for (ssize_t logical = beg; logical < beg + len; logical++) {
1069
- ssize_t idx = logical_to_physical(logical, lsb_first);
1070
- unsigned char mask = (unsigned char)(1u << (idx % 8));
1071
- switch (op) {
1072
- case SB_MUT_SET: ptr[idx / 8] |= mask; break;
1073
- case SB_MUT_CLEAR: ptr[idx / 8] &= (unsigned char)~mask; break;
1074
- case SB_MUT_FLIP: ptr[idx / 8] ^= mask; break;
1075
- }
1209
+ rb_str_modify(self);
1210
+ ptr = (unsigned char *)RSTRING_PTR(self);
1211
+
1212
+ for (ssize_t logical = bit_offset; logical < bit_offset + bit_length; logical++) {
1213
+ ssize_t idx = logical_to_physical(logical, lsb_first);
1214
+ unsigned char mask = (unsigned char)(1u << (idx % 8));
1215
+ switch (op) {
1216
+ case SB_MUT_SET: ptr[idx / 8] |= mask; break;
1217
+ case SB_MUT_CLEAR: ptr[idx / 8] &= (unsigned char)~mask; break;
1218
+ case SB_MUT_FLIP: ptr[idx / 8] ^= mask; break;
1076
1219
  }
1077
- return self;
1078
1220
  }
1079
-
1080
- rb_raise(rb_eTypeError, "bit index must be an integer or Range");
1081
- UNREACHABLE_RETURN(Qnil);
1221
+ return self;
1082
1222
  }
1083
1223
 
1084
1224
  static VALUE
@@ -1110,13 +1250,20 @@ check_binary_op_lengths(VALUE self, VALUE other)
1110
1250
  }
1111
1251
  }
1112
1252
 
1253
+ /* Allocate the destination buffer of a non-destructive bitwise operation.
1254
+ *
1255
+ * Bit operations read the receiver as a raw byte sequence, so the bytes they
1256
+ * produce need not be valid in the receiver's encoding; the result is always
1257
+ * BINARY. The destructive (`!`) variants leave the receiver's encoding alone
1258
+ * instead, which is what String#setbyte does for the same reason. */
1113
1259
  static VALUE
1114
1260
  alloc_result(VALUE self)
1115
1261
  {
1116
1262
  ssize_t len = RSTRING_LEN(self);
1117
1263
  VALUE result = rb_str_buf_new(len);
1118
1264
  rb_str_resize(result, len);
1119
- rb_enc_associate(result, rb_enc_get(self));
1265
+ rb_enc_associate(result, rb_ascii8bit_encoding());
1266
+ ENC_CODERANGE_CLEAR(result);
1120
1267
  return result;
1121
1268
  }
1122
1269
 
@@ -1231,6 +1378,7 @@ SB_DEFINE_BINARY_KERNEL(kern_xor, SB_XOR_WORD, SB_XOR_BYTE)
1231
1378
  static VALUE \
1232
1379
  rb_str_bitwise_##op_name(VALUE self, VALUE other) \
1233
1380
  { \
1381
+ StringValue(other); \
1234
1382
  check_binary_op_lengths(self, other); \
1235
1383
  ssize_t len = RSTRING_LEN(self); \
1236
1384
  VALUE result = alloc_result(self); \
@@ -1242,6 +1390,7 @@ SB_DEFINE_BINARY_KERNEL(kern_xor, SB_XOR_WORD, SB_XOR_BYTE)
1242
1390
  static VALUE \
1243
1391
  rb_str_bitwise_##op_name##_bang(VALUE self, VALUE other) \
1244
1392
  { \
1393
+ StringValue(other); \
1245
1394
  check_binary_op_lengths(self, other); \
1246
1395
  rb_str_modify(self); \
1247
1396
  ssize_t len = RSTRING_LEN(self); \
@@ -1336,7 +1485,7 @@ rb_str_each_bit_field(int argc, VALUE *argv, VALUE self)
1336
1485
  rb_raise(rb_eArgError, "bitlen must be positive");
1337
1486
  }
1338
1487
  if (bl > 64) {
1339
- rb_raise(rb_eArgError, "bitlen must be <= 64 (got %ld)", bl);
1488
+ rb_raise(rb_eArgError, "bitlen must be <= 64 (got %" PRIdPTR ")", (intptr_t)bl);
1340
1489
  }
1341
1490
  bitlens[f] = bl;
1342
1491
  step += bl;
@@ -1345,8 +1494,8 @@ rb_str_each_bit_field(int argc, VALUE *argv, VALUE self)
1345
1494
  int lsb_first = parse_lsb_first_opt(opts);
1346
1495
 
1347
1496
  ssize_t src_len = RSTRING_LEN(self);
1348
- ssize_t total_bits = src_len * 8;
1349
- ssize_t iterations = total_bits / step;
1497
+ int64_t total_bits = SB_BIT_LEN(src_len);
1498
+ ssize_t iterations = (ssize_t)(total_bits / step);
1350
1499
 
1351
1500
  VALUE *field_vals = ALLOCA_N(VALUE, num_fields);
1352
1501
 
@@ -1390,7 +1539,7 @@ rb_str_bit_fields(int argc, VALUE *argv, VALUE self)
1390
1539
  rb_raise(rb_eArgError, "bitlen must be positive");
1391
1540
  }
1392
1541
  if (bl > 64) {
1393
- rb_raise(rb_eArgError, "bitlen must be <= 64 (got %ld)", bl);
1542
+ rb_raise(rb_eArgError, "bitlen must be <= 64 (got %" PRIdPTR ")", (intptr_t)bl);
1394
1543
  }
1395
1544
  bitlens[f] = bl;
1396
1545
  step += bl;
@@ -1399,8 +1548,8 @@ rb_str_bit_fields(int argc, VALUE *argv, VALUE self)
1399
1548
  int lsb_first = parse_lsb_first_opt(opts);
1400
1549
 
1401
1550
  ssize_t src_len = RSTRING_LEN(self);
1402
- ssize_t total_bits = src_len * 8;
1403
- ssize_t iterations = total_bits / step;
1551
+ int64_t total_bits = SB_BIT_LEN(src_len);
1552
+ ssize_t iterations = (ssize_t)(total_bits / step);
1404
1553
 
1405
1554
  int have_block = rb_block_given_p();
1406
1555
  VALUE result = have_block ? Qnil : rb_ary_new_capa(iterations);
@@ -1438,13 +1587,13 @@ rb_str_bit_fields(int argc, VALUE *argv, VALUE self)
1438
1587
  * - remaining bytes: ctz on the inverted byte
1439
1588
  *
1440
1589
  * Porting to Ruby Core:
1441
- * 1. Move to string.c alongside bit_at and each_bit.
1590
+ * 1. Move to string.c alongside bit_get and each_bit.
1442
1591
  * 2. Share sb_ctz8 / sb_ctzll with the existing set-bit helpers.
1443
1592
  */
1444
1593
  static ssize_t
1445
1594
  count_run_lsb(const unsigned char *src, ssize_t src_len, ssize_t bit_offset, int target)
1446
1595
  {
1447
- ssize_t max_run = src_len * 8 - bit_offset;
1596
+ int64_t max_run = SB_BIT_LEN(src_len) - bit_offset;
1448
1597
  ssize_t byte_idx = bit_offset >> 3;
1449
1598
  int bit_off = bit_offset & 7;
1450
1599
  ssize_t count = 0;
@@ -1461,7 +1610,7 @@ count_run_lsb(const unsigned char *src, ssize_t src_len, ssize_t bit_offset, int
1461
1610
  count += run;
1462
1611
  byte_idx++;
1463
1612
  if (run < remaining)
1464
- return count < max_run ? count : max_run;
1613
+ return (ssize_t)(count < max_run ? count : max_run);
1465
1614
  }
1466
1615
 
1467
1616
  #if SB_LITTLE_ENDIAN
@@ -1475,7 +1624,7 @@ count_run_lsb(const unsigned char *src, ssize_t src_len, ssize_t bit_offset, int
1475
1624
  byte_idx += 8;
1476
1625
  } else {
1477
1626
  count += sb_ctzll(~word);
1478
- return count < max_run ? count : max_run;
1627
+ return (ssize_t)(count < max_run ? count : max_run);
1479
1628
  }
1480
1629
  }
1481
1630
  #endif
@@ -1490,11 +1639,11 @@ count_run_lsb(const unsigned char *src, ssize_t src_len, ssize_t bit_offset, int
1490
1639
  byte_idx++;
1491
1640
  } else {
1492
1641
  count += sb_ctz8(~b);
1493
- return count < max_run ? count : max_run;
1642
+ return (ssize_t)(count < max_run ? count : max_run);
1494
1643
  }
1495
1644
  }
1496
1645
 
1497
- return count < max_run ? count : max_run;
1646
+ return (ssize_t)(count < max_run ? count : max_run);
1498
1647
  }
1499
1648
 
1500
1649
  /* Return the length of the consecutive run of `bit` starting at pos, or nil. */
@@ -1510,9 +1659,15 @@ rb_str_bit_run_count(int argc, VALUE *argv, VALUE self)
1510
1659
  rb_raise(rb_eTypeError, "position must be an integer");
1511
1660
  }
1512
1661
  int target = parse_bit_target(bit_val);
1513
- ssize_t bit_offset = integer_to_bit_idx(bit_offset_v);
1514
1662
  ssize_t src_len = RSTRING_LEN(self);
1515
- if (bit_offset < 0 || bit_offset >= src_len * 8) return Qnil;
1663
+ /* A position with no run starting at it and a position outside the string
1664
+ * are the same answer here -- nil -- so a negative or past-the-end offset
1665
+ * is not an error (see ProposedMethods.md, "Why bit_run_count never
1666
+ * returns 0"). */
1667
+ uint64_t off;
1668
+ if (sb_bit_position_soft(bit_offset_v, &off) < 0) return Qnil;
1669
+ if (off >= (uint64_t)SB_BIT_LEN(src_len)) return Qnil;
1670
+ ssize_t bit_offset = (ssize_t)off;
1516
1671
 
1517
1672
  const unsigned char *src = (const unsigned char *)RSTRING_PTR(self);
1518
1673
  if (lsb_first) {
@@ -1523,7 +1678,7 @@ rb_str_bit_run_count(int argc, VALUE *argv, VALUE self)
1523
1678
  if (logical_get_bit(src, bit_offset, 0) != target) return Qnil;
1524
1679
 
1525
1680
  ssize_t run = 1;
1526
- ssize_t total_bits = src_len * 8;
1681
+ int64_t total_bits = SB_BIT_LEN(src_len);
1527
1682
  while (bit_offset + run < total_bits && logical_get_bit(src, bit_offset + run, 0) == target) {
1528
1683
  run++;
1529
1684
  }
@@ -1545,8 +1700,8 @@ static void
1545
1700
  emit_bit_runs(VALUE self, int lsb_first, ssize_t start_offset, VALUE ary)
1546
1701
  {
1547
1702
  ssize_t src_len = RSTRING_LEN(self);
1548
- if (src_len == 0 || start_offset >= src_len * 8) return;
1549
- ssize_t total_bits = src_len * 8;
1703
+ int64_t total_bits = SB_BIT_LEN(src_len);
1704
+ if (src_len == 0 || start_offset >= total_bits) return;
1550
1705
  ssize_t offset = start_offset;
1551
1706
 
1552
1707
  #define SB_EMIT_TRIPLE(bval, oval, lval) \
@@ -1587,7 +1742,7 @@ rb_str_each_bit_run(int argc, VALUE *argv, VALUE self)
1587
1742
  rb_scan_args(argc, argv, "01:", &start_offset_v, &opts);
1588
1743
  validate_option_hash(opts, SB_KW_LSB_FIRST);
1589
1744
  int lsb_first = parse_lsb_first_opt(opts);
1590
- ssize_t start_offset = parse_start_offset(start_offset_v);
1745
+ ssize_t start_offset = parse_start_offset(self, start_offset_v);
1591
1746
 
1592
1747
  emit_bit_runs(self, lsb_first, start_offset, Qnil);
1593
1748
  return self;
@@ -1601,7 +1756,7 @@ rb_str_bit_runs(int argc, VALUE *argv, VALUE self)
1601
1756
  rb_scan_args(argc, argv, "01:", &start_offset_v, &opts);
1602
1757
  validate_option_hash(opts, SB_KW_LSB_FIRST);
1603
1758
  int lsb_first = parse_lsb_first_opt(opts);
1604
- ssize_t start_offset = parse_start_offset(start_offset_v);
1759
+ ssize_t start_offset = parse_start_offset(self, start_offset_v);
1605
1760
 
1606
1761
  if (rb_block_given_p()) {
1607
1762
  emit_bit_runs(self, lsb_first, start_offset, Qnil);
@@ -1618,52 +1773,47 @@ static VALUE
1618
1773
  rb_str_bit_splice(int argc, VALUE *argv, VALUE self)
1619
1774
  {
1620
1775
  ssize_t dst_bit_off, dst_bit_len;
1621
- ssize_t src_bit_off, src_bit_len;
1776
+ ssize_t src_bit_off;
1622
1777
  VALUE str;
1623
- ssize_t dst_total = RSTRING_LEN(self) * 8;
1778
+ int64_t dst_total = SB_BIT_LEN(RSTRING_LEN(self));
1624
1779
  VALUE v0, v1, v2, v3, opts;
1625
1780
 
1626
1781
  int n_pos = rb_scan_args(argc, argv, "22:", &v0, &v1, &v2, &v3, &opts);
1627
1782
  validate_option_hash(opts, SB_KW_LSB_FIRST);
1628
1783
  int lsb_first = parse_lsb_first_opt(opts);
1629
1784
 
1630
- if (n_pos == 2 && rb_obj_is_kind_of(v0, rb_cRange)) {
1631
- /* bit_splice(range, str) */
1632
- sb_range_validate_endpoints(v0);
1633
- ssize_t beg, len;
1634
- sb_range_beg_len(v0, &beg, &len, dst_total, 1);
1635
- dst_bit_off = beg;
1636
- dst_bit_len = len;
1637
- str = v1;
1638
- Check_Type(str, T_STRING);
1639
- src_bit_off = 0;
1640
- src_bit_len = dst_bit_len;
1641
- }
1642
- else if (n_pos == 3 && rb_obj_is_kind_of(v0, rb_cRange)) {
1643
- /* bit_splice(range, str, str_bit_index) */
1644
- sb_range_validate_endpoints(v0);
1785
+ uint64_t dst_off_u, dst_len_u, src_off_u, src_len_u;
1786
+
1787
+ if (n_pos <= 3 && rb_obj_is_kind_of(v0, rb_cRange)) {
1788
+ /* bit_splice(range, str) and bit_splice(range, str, str_bit_index) */
1645
1789
  ssize_t beg, len;
1646
- sb_range_beg_len(v0, &beg, &len, dst_total, 1);
1647
- dst_bit_off = beg;
1648
- dst_bit_len = len;
1790
+ if (sb_bit_range_beg_len(v0, &beg, &len, dst_total) == SB_RANGE_OUT) {
1791
+ rb_raise(rb_eIndexError,
1792
+ "bit_splice: destination range starts past the end "
1793
+ "(total %" PRId64 " bits)", (int64_t)dst_total);
1794
+ }
1795
+ dst_off_u = (uint64_t)beg;
1796
+ dst_len_u = (uint64_t)len;
1649
1797
  str = v1;
1650
1798
  Check_Type(str, T_STRING);
1651
- if (!rb_integer_type_p(v2)) {
1652
- rb_raise(rb_eTypeError, "third argument must be an Integer");
1799
+ if (n_pos == 3) {
1800
+ if (!rb_integer_type_p(v2)) {
1801
+ rb_raise(rb_eTypeError, "third argument must be an Integer");
1802
+ }
1803
+ src_off_u = sb_bit_offset(v2);
1653
1804
  }
1654
- ssize_t src_total = RSTRING_LEN(str) * 8;
1655
- src_bit_off = integer_to_bit_idx(v2);
1656
- if (src_bit_off < 0) src_bit_off += src_total;
1657
- src_bit_len = dst_bit_len;
1805
+ else {
1806
+ src_off_u = 0;
1807
+ }
1808
+ src_len_u = dst_len_u;
1658
1809
  }
1659
1810
  else if (n_pos == 3) {
1660
1811
  /* bit_splice(bit_index, bit_length, str) */
1661
1812
  if (!rb_integer_type_p(v0) || !rb_integer_type_p(v1)) {
1662
1813
  rb_raise(rb_eTypeError, "bit index and length must be integers");
1663
1814
  }
1664
- dst_bit_off = integer_to_bit_idx(v0);
1665
- dst_bit_len = integer_to_bit_idx(v1);
1666
- if (dst_bit_off < 0) dst_bit_off += dst_total;
1815
+ dst_off_u = sb_bit_offset(v0);
1816
+ dst_len_u = sb_bit_length(v1);
1667
1817
 
1668
1818
  /*
1669
1819
  * Integer source support was prototyped here, but it is intentionally
@@ -1677,40 +1827,56 @@ rb_str_bit_splice(int argc, VALUE *argv, VALUE self)
1677
1827
 
1678
1828
  str = v2;
1679
1829
  Check_Type(str, T_STRING);
1680
- src_bit_off = 0;
1681
- src_bit_len = dst_bit_len;
1830
+ src_off_u = 0;
1831
+ src_len_u = dst_len_u;
1682
1832
  }
1683
1833
  else if (n_pos == 4) {
1684
1834
  /* bit_splice(bit_index, bit_length, str, str_bit_index) */
1685
1835
  if (!rb_integer_type_p(v0) || !rb_integer_type_p(v1) || !rb_integer_type_p(v3)) {
1686
1836
  rb_raise(rb_eTypeError, "bit indices and lengths must be integers");
1687
1837
  }
1688
- dst_bit_off = integer_to_bit_idx(v0);
1689
- dst_bit_len = integer_to_bit_idx(v1);
1690
- if (dst_bit_off < 0) dst_bit_off += dst_total;
1838
+ dst_off_u = sb_bit_offset(v0);
1839
+ dst_len_u = sb_bit_length(v1);
1691
1840
  str = v2;
1692
1841
  Check_Type(str, T_STRING);
1693
- ssize_t src_total = RSTRING_LEN(str) * 8;
1694
- src_bit_off = integer_to_bit_idx(v3);
1695
- if (src_bit_off < 0) src_bit_off += src_total;
1696
- src_bit_len = dst_bit_len;
1842
+ src_off_u = sb_bit_offset(v3);
1843
+ src_len_u = dst_len_u;
1697
1844
  }
1698
1845
  else {
1699
1846
  rb_raise(rb_eArgError,
1700
1847
  "wrong number of arguments (given %d, expected 2, 3, or 4)", n_pos);
1701
1848
  }
1702
1849
 
1703
- if (dst_bit_off < 0 || dst_bit_len < 0 || dst_bit_off + dst_bit_len > dst_total) {
1850
+ /* Both ranges are checked in 64 bits: a position past the end of the
1851
+ * string is well-formed input here, so it must reach this bounds check
1852
+ * rather than being rejected while it is parsed. */
1853
+ if (dst_off_u > (uint64_t)dst_total || dst_len_u > (uint64_t)dst_total - dst_off_u) {
1704
1854
  rb_raise(rb_eIndexError,
1705
- "bit_splice: destination range [%ld, %ld] out of bounds (total %ld bits)",
1706
- dst_bit_off, dst_bit_len, dst_total);
1855
+ "bit_splice: destination range [%" PRIu64 ", %" PRIu64
1856
+ "] out of bounds (total %" PRId64 " bits)",
1857
+ dst_off_u, dst_len_u, (int64_t)dst_total);
1707
1858
  }
1708
1859
 
1709
- ssize_t src_total_bits = RSTRING_LEN(str) * 8;
1710
- if (src_bit_off < 0 || src_bit_len < 0 || src_bit_off + src_bit_len > src_total_bits) {
1860
+ int64_t src_total_bits = SB_BIT_LEN(RSTRING_LEN(str));
1861
+ if (src_off_u > (uint64_t)src_total_bits ||
1862
+ src_len_u > (uint64_t)src_total_bits - src_off_u) {
1711
1863
  rb_raise(rb_eIndexError,
1712
- "bit_splice: source range [%ld, %ld] out of bounds (total %ld bits)",
1713
- src_bit_off, src_bit_len, src_total_bits);
1864
+ "bit_splice: source range [%" PRIu64 ", %" PRIu64
1865
+ "] out of bounds (total %" PRId64 " bits)",
1866
+ src_off_u, src_len_u, (int64_t)src_total_bits);
1867
+ }
1868
+
1869
+ /* Both ranges are inside their strings now, so narrowing only fails on an
1870
+ * ILP32 build whose bit length exceeds the internal index (see
1871
+ * sb_narrow_bit_pos). */
1872
+ dst_bit_off = sb_narrow_bit_pos(dst_off_u, dst_total);
1873
+ dst_bit_len = (ssize_t)dst_len_u;
1874
+ if (dst_len_u > (uint64_t)(sb_addressable_bits(dst_total) - dst_bit_off)) {
1875
+ rb_raise(rb_eArgError, "bit index out of representable range");
1876
+ }
1877
+ src_bit_off = sb_narrow_bit_pos(src_off_u, src_total_bits);
1878
+ if (src_len_u > (uint64_t)(sb_addressable_bits(src_total_bits) - src_bit_off)) {
1879
+ rb_raise(rb_eArgError, "bit index out of representable range");
1714
1880
  }
1715
1881
 
1716
1882
  if (dst_bit_len == 0) return self;
@@ -1840,8 +2006,9 @@ rb_ary_mask(int argc, VALUE *argv, VALUE self)
1840
2006
  ssize_t needed = (ary_len + 7) >> 3;
1841
2007
  if (needed > bmp_len)
1842
2008
  rb_raise(rb_eArgError,
1843
- "bitmap too short: need %ld bytes for %ld elements, got %ld",
1844
- needed, ary_len, bmp_len);
2009
+ "bitmap too short: need %" PRIdPTR " bytes for %" PRIdPTR
2010
+ " elements, got %" PRIdPTR,
2011
+ (intptr_t)needed, (intptr_t)ary_len, (intptr_t)bmp_len);
1845
2012
 
1846
2013
  if (!lsb_first) {
1847
2014
  for (ssize_t i = 0; i < ary_len; i++) {
@@ -1885,8 +2052,9 @@ rb_ary_mask_bang(int argc, VALUE *argv, VALUE self)
1885
2052
  ssize_t needed = (ary_len + 7) >> 3;
1886
2053
  if (needed > bmp_len)
1887
2054
  rb_raise(rb_eArgError,
1888
- "bitmap too short: need %ld bytes for %ld elements, got %ld",
1889
- needed, ary_len, bmp_len);
2055
+ "bitmap too short: need %" PRIdPTR " bytes for %" PRIdPTR
2056
+ " elements, got %" PRIdPTR,
2057
+ (intptr_t)needed, (intptr_t)ary_len, (intptr_t)bmp_len);
1890
2058
 
1891
2059
  if (!lsb_first) {
1892
2060
  for (ssize_t i = 0; i < ary_len; i++) {
@@ -1916,7 +2084,8 @@ Init_string_bits(void)
1916
2084
  sym_lsb_first = ID2SYM(rb_intern("lsb_first"));
1917
2085
  sym_invert = ID2SYM(rb_intern("invert"));
1918
2086
 
1919
- rb_define_method(rb_cString, "bit_at", rb_str_bit_at, -1);
2087
+ rb_define_method(rb_cString, "bit_get", rb_str_bit_get, -1);
2088
+ rb_define_method(rb_cString, "bit_set?", rb_str_bit_set_p, -1);
1920
2089
  rb_define_method(rb_cString, "bit_count", rb_str_bit_count, -1);
1921
2090
  rb_define_method(rb_cString, "each_bit", rb_str_each_bit, -1);
1922
2091
  rb_define_method(rb_cString, "bits", rb_str_bits, -1);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: string_bits
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - HASUMI Hitoshi
@@ -91,6 +91,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
91
91
  - - ">="
92
92
  - !ruby/object:Gem::Version
93
93
  version: '3.0'
94
+ - - "<"
95
+ - !ruby/object:Gem::Version
96
+ version: 4.1.dev
94
97
  required_rubygems_version: !ruby/object:Gem::Requirement
95
98
  requirements:
96
99
  - - ">="