diff --git a/src/ops/collection.c b/src/ops/collection.c index 798096d7..c25c45f9 100644 --- a/src/ops/collection.c +++ b/src/ops/collection.c @@ -1772,6 +1772,12 @@ ray_t* ray_take_fn(ray_t* vec, ray_t* n_obj) { return ray_error("type", "take: range take unsupported for %s", ray_type_name(vec->type)); } + /* Every scalar-count branch below turns the count into a magnitude with + * `n < 0 ? -n : n`; `-INT64_MIN` is signed-overflow UB and |INT64_MIN| is + * unallocatable anyway, so reject an INT64_MIN count (also the i64 null + * sentinel) once here, up front, for all of them. */ + if (ray_is_atom(n_obj) && is_numeric(n_obj) && as_i64(n_obj) == INT64_MIN) + return ray_error("range", "take: count magnitude out of range"); /* Char take: (take 'a' n) → string of n copies of char */ if (ray_is_atom(vec) && vec->type == -RAY_STR && ray_str_len(vec) == 1 && ray_is_atom(n_obj) && is_numeric(n_obj)) { int64_t n = as_i64(n_obj); @@ -2015,7 +2021,10 @@ ray_t* ray_drop_fn(ray_t* vec, ray_t* n_obj) { start = n < len ? n : len; amount = len - start; } else { - int64_t cut = -n; + /* `-INT64_MIN` is signed-overflow UB; a drop-from-end of that + * magnitude removes the whole collection (|n| >= len), so treat it as + * cut == len (amount 0) rather than negating. */ + int64_t cut = (n == INT64_MIN) ? len : -n; amount = cut < len ? len - cut : 0; } return collection_slice(vec, start, amount); diff --git a/test/rfl/collection/drop_cut_rotate_cross.rfl b/test/rfl/collection/drop_cut_rotate_cross.rfl index bbf5a387..74b9538b 100644 --- a/test/rfl/collection/drop_cut_rotate_cross.rfl +++ b/test/rfl/collection/drop_cut_rotate_cross.rfl @@ -4,6 +4,9 @@ (drop [1 2 3 4 5] -2) -- [1 2 3] (drop [1 2 3] 10) -- [] (drop [1 2 3] -10) -- [] +;; INT64_MIN count: `-INT64_MIN` is signed-overflow UB (was tripped in +;; ray_drop_fn); |n| that large drops the whole collection. +(drop [1 2 3] -9223372036854775808) -- [] (drop "abcdef" 2) -- "cdef" (drop "abcdef" -2) -- "abcd" (drop ['a 'b 'c] 1) -- ['b 'c] diff --git a/test/rfl/collection/take.rfl b/test/rfl/collection/take.rfl index 92e1ef1d..bb1a954a 100644 --- a/test/rfl/collection/take.rfl +++ b/test/rfl/collection/take.rfl @@ -175,3 +175,12 @@ (count (take [1 2 3 4 5] [2 0])) -- 0 ;; range take with start = count → empty (count (take [1 2 3 4 5] [5 3])) -- 0 +;; INT64_MIN count: `-INT64_MIN` is signed-overflow UB (was tripped computing +;; the magnitude in every scalar-count take branch — vector, string, char, +;; scalar, list). Its magnitude is unrepresentable as int64, so it is a range +;; error, not UB followed by a bogus allocation. +(take [1 2 3] -9223372036854775808) !- range +(take "hello" -9223372036854775808) !- range +(take "a" -9223372036854775808) !- range +(take 42 -9223372036854775808) !- range +(take (list 1 2 3) -9223372036854775808) !- range