From 4e0bb58a0a374b40b7691e7b7aa88e759a0fc9f2 Mon Sep 17 00:00:00 2001 From: Luke Jahnke Date: Mon, 29 Dec 2025 15:51:17 +1000 Subject: [PATCH 1/5] fix underflow --- pack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pack.c b/pack.c index 3a5c1bfb9677cf..87a5c6787134c3 100644 --- a/pack.c +++ b/pack.c @@ -302,7 +302,7 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer) else if (ISDIGIT(*p)) { errno = 0; len = STRTOUL(p, (char**)&p, 10); - if (errno) { + if (len < 0 || errno) { rb_raise(rb_eRangeError, "pack length too big"); } } From 72627d85e337e5d8c7fa5738dc4ec7f253f0738e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 29 Dec 2025 12:52:29 +0900 Subject: [PATCH 2/5] Declare `rb_data_typed_t` parameters and return values as nonull --- include/ruby/internal/core/rtypeddata.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 55b7d2b47dc27f..0f430f0c6aaf5d 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -396,6 +396,7 @@ RBIMPL_ATTR_NONNULL((3)) */ VALUE rb_data_typed_object_wrap(VALUE klass, void *datap, const rb_data_type_t *type); +RBIMPL_ATTR_NONNULL((3)) /** * Identical to rb_data_typed_object_wrap(), except it allocates a new data * region internally instead of taking an existing one. The allocation is done @@ -411,6 +412,7 @@ VALUE rb_data_typed_object_wrap(VALUE klass, void *datap, const rb_data_type_t * */ VALUE rb_data_typed_object_zalloc(VALUE klass, size_t size, const rb_data_type_t *type); +RBIMPL_ATTR_NONNULL(()) /** * Checks for the domestic relationship between the two. * @@ -425,6 +427,7 @@ VALUE rb_data_typed_object_zalloc(VALUE klass, size_t size, const rb_data_type_t */ int rb_typeddata_inherited_p(const rb_data_type_t *child, const rb_data_type_t *parent); +RBIMPL_ATTR_NONNULL((2)) /** * Checks if the given object is of given kind. * @@ -435,6 +438,7 @@ int rb_typeddata_inherited_p(const rb_data_type_t *child, const rb_data_type_t * */ int rb_typeddata_is_kind_of(VALUE obj, const rb_data_type_t *data_type); +RBIMPL_ATTR_NONNULL((2)) /** * Identical to rb_typeddata_is_kind_of(), except it raises exceptions instead * of returning false. @@ -586,7 +590,7 @@ RTYPEDDATA_P(VALUE obj) RBIMPL_ATTR_PURE_UNLESS_DEBUG() RBIMPL_ATTR_ARTIFICIAL() -/* :TODO: can this function be __attribute__((returns_nonnull)) or not? */ +RBIMPL_ATTR_RETURNS_NONNULL() /** * Queries for the type of given object. * @@ -605,7 +609,9 @@ RTYPEDDATA_TYPE(VALUE obj) #endif VALUE type = RTYPEDDATA(obj)->type & TYPED_DATA_PTR_MASK; - return RBIMPL_CAST((const rb_data_type_t *)type); + const rb_data_type_t *ptr = RBIMPL_CAST((const rb_data_type_t *)type); + RBIMPL_ASSERT_OR_ASSUME(ptr); + return ptr; } RBIMPL_ATTR_ARTIFICIAL() @@ -650,6 +656,7 @@ rbimpl_check_typeddata(VALUE obj, const rb_data_type_t *expected_type) #define TypedData_Get_Struct(obj,type,data_type,sval) \ ((sval) = RBIMPL_CAST((type *)rbimpl_check_typeddata((obj), (data_type)))) +RBIMPL_ATTR_NONNULL((2)) /** * While we don't stop you from using this function, it seems to be an * implementation detail of #TypedData_Make_Struct, which is preferred over From 0f64da9672d88921439f6fdb306d16fece9b9c90 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 29 Dec 2025 18:14:28 +0900 Subject: [PATCH 3/5] Make `rb_check_typeddata` and `rbimpl_check_typeddata` identical --- error.c | 53 ++++++-------- include/ruby/internal/core/rtypeddata.h | 96 +++++++++++++++++++++---- internal/error.h | 3 +- 3 files changed, 104 insertions(+), 48 deletions(-) diff --git a/error.c b/error.c index e1a01b985aae16..f21a682d65d352 100644 --- a/error.c +++ b/error.c @@ -1313,6 +1313,20 @@ rb_builtin_class_name(VALUE x) COLDFUNC NORETURN(static void unexpected_type(VALUE, int, int)); #define UNDEF_LEAKED "undef leaked to the Ruby space" +void +rb_unexpected_typeddata(const rb_data_type_t *actual, const rb_data_type_t *expected) +{ + rb_raise(rb_eTypeError, "wrong argument type %s (expected %s)", + actual->wrap_struct_name, expected->wrap_struct_name); +} + +void +rb_unexpected_object_type(VALUE obj, const char *expected) +{ + rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected %s)", + displaying_class_of(obj), expected); +} + static void unexpected_type(VALUE x, int xt, int t) { @@ -1320,9 +1334,7 @@ unexpected_type(VALUE x, int xt, int t) VALUE mesg, exc = rb_eFatal; if (tname) { - mesg = rb_sprintf("wrong argument type %"PRIsVALUE" (expected %s)", - displaying_class_of(x), tname); - exc = rb_eTypeError; + rb_unexpected_object_type(x, tname); } else if (xt > T_MASK && xt <= 0x3f) { mesg = rb_sprintf("unknown type 0x%x (0x%x given, probably comes" @@ -1367,24 +1379,18 @@ rb_unexpected_type(VALUE x, int t) unexpected_type(x, TYPE(x), t); } +#undef rb_typeddata_inherited_p int rb_typeddata_inherited_p(const rb_data_type_t *child, const rb_data_type_t *parent) { - while (child) { - if (child == parent) return 1; - child = child->parent; - } - return 0; + return rb_typeddata_inherited_p_inline(child, parent); } +#undef rb_typeddata_is_kind_of int rb_typeddata_is_kind_of(VALUE obj, const rb_data_type_t *data_type) { - if (!RB_TYPE_P(obj, T_DATA) || - !RTYPEDDATA_P(obj) || !rb_typeddata_inherited_p(RTYPEDDATA_TYPE(obj), data_type)) { - return 0; - } - return 1; + return rb_typeddata_is_kind_of_inline(obj, data_type); } #undef rb_typeddata_is_instance_of @@ -1397,26 +1403,7 @@ rb_typeddata_is_instance_of(VALUE obj, const rb_data_type_t *data_type) void * rb_check_typeddata(VALUE obj, const rb_data_type_t *data_type) { - VALUE actual; - - if (!RB_TYPE_P(obj, T_DATA)) { - actual = displaying_class_of(obj); - } - else if (!RTYPEDDATA_P(obj)) { - actual = displaying_class_of(obj); - } - else if (!rb_typeddata_inherited_p(RTYPEDDATA_TYPE(obj), data_type)) { - const char *name = RTYPEDDATA_TYPE(obj)->wrap_struct_name; - actual = rb_str_new_cstr(name); /* or rb_fstring_cstr? not sure... */ - } - else { - return RTYPEDDATA_GET_DATA(obj); - } - - const char *expected = data_type->wrap_struct_name; - rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected %s)", - actual, expected); - UNREACHABLE_RETURN(NULL); + return rbimpl_check_typeddata(obj, data_type); } /* exception classes */ diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 0f430f0c6aaf5d..8952ba91810aac 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -450,6 +450,36 @@ RBIMPL_ATTR_NONNULL((2)) * @post Upon successful return `obj`'s type is guaranteed `data_type`. */ void *rb_check_typeddata(VALUE obj, const rb_data_type_t *data_type); + +RBIMPL_ATTR_NORETURN() +RBIMPL_ATTR_NONNULL((2)) +/** + * @private + * + * Fails with the given object's type incompatibility to the type. + * + * This is an implementation detail of Check_Type. People don't use it + * directly. + * + * @param[in] obj The object in question. + * @param[in] expected Name of expected data type of `obj`. + */ +void rb_unexpected_object_type(VALUE obj, const char *expected); + +RBIMPL_ATTR_NORETURN() +RBIMPL_ATTR_NONNULL(()) +/** + * @private + * + * Fails with the given object's type incompatibility to the type. + * + * This is an implementation detail of #TypedData_Make_Struct. People don't + * use it directly. + * + * @param[in] actual Actual data type. + * @param[in] expected Expected data type. + */ +void rb_unexpected_typeddata(const rb_data_type_t *actual, const rb_data_type_t *expected); RBIMPL_SYMBOL_EXPORT_END() /** @@ -565,6 +595,27 @@ rbimpl_rtypeddata_p(VALUE obj) return FL_TEST_RAW(obj, RUBY_TYPED_FL_IS_TYPED_DATA); } +RBIMPL_ATTR_PURE() +RBIMPL_ATTR_ARTIFICIAL() +/** + * @private + * + * Identical to rbimpl_rtypeddata_p(), except it is allowed to call on non-data + * objects. + * + * This is an implementation detail of inline functions defined in this file. + * People don't use it directly. + * + * @param[in] obj Object in question + * @retval true `obj` is an instance of ::RTypedData. + * @retval false `obj` is not an instance of ::RTypedData + */ +static inline bool +rbimpl_obj_typeddata_p(VALUE obj) +{ + return RB_TYPE_P(obj, RUBY_T_DATA) && rbimpl_rtypeddata_p(obj); +} + RBIMPL_ATTR_PURE_UNLESS_DEBUG() RBIMPL_ATTR_ARTIFICIAL() /** @@ -615,6 +666,29 @@ RTYPEDDATA_TYPE(VALUE obj) } RBIMPL_ATTR_ARTIFICIAL() +RBIMPL_ATTR_NONNULL(()) +static inline bool +rb_typeddata_inherited_p_inline(const rb_data_type_t *child, const rb_data_type_t *parent) +{ + do { + if (RB_LIKELY(child == parent)) return true; + } while ((child = child->parent) != NULL); + return false; +} +#define rb_typeddata_inherited_p rb_typeddata_inherited_p_inline + +RBIMPL_ATTR_ARTIFICIAL() +RBIMPL_ATTR_NONNULL((2)) +static inline bool +rb_typeddata_is_kind_of_inline(VALUE obj, const rb_data_type_t *data_type) +{ + if (RB_UNLIKELY(!rbimpl_obj_typeddata_p(obj))) return false; + return rb_typeddata_inherited_p(RTYPEDDATA_TYPE(obj), data_type); +} +#define rb_typeddata_is_kind_of rb_typeddata_is_kind_of_inline + +RBIMPL_ATTR_ARTIFICIAL() +RBIMPL_ATTR_NONNULL((2)) /** * @private * @@ -624,22 +698,16 @@ RBIMPL_ATTR_ARTIFICIAL() static inline void * rbimpl_check_typeddata(VALUE obj, const rb_data_type_t *expected_type) { - if (RB_LIKELY(RB_TYPE_P(obj, T_DATA) && RTYPEDDATA_P(obj))) { - const rb_data_type_t *actual_type = RTYPEDDATA_TYPE(obj); - void *data = RTYPEDDATA_GET_DATA(obj); - if (RB_LIKELY(actual_type == expected_type)) { - return data; - } - - while (actual_type) { - actual_type = actual_type->parent; - if (actual_type == expected_type) { - return data; - } - } + if (RB_UNLIKELY(!rbimpl_obj_typeddata_p(obj))) { + rb_unexpected_object_type(obj, expected_type->wrap_struct_name); + } + + const rb_data_type_t *actual_type = RTYPEDDATA_TYPE(obj); + if (RB_UNLIKELY(!rb_typeddata_inherited_p(actual_type, expected_type))){ + rb_unexpected_typeddata(actual_type, expected_type); } - return rb_check_typeddata(obj, expected_type); + return RTYPEDDATA_GET_DATA(obj); } diff --git a/internal/error.h b/internal/error.h index cecaa5c4a8d69e..4b41aee77b00ec 100644 --- a/internal/error.h +++ b/internal/error.h @@ -235,10 +235,11 @@ rb_key_err_raise(VALUE mesg, VALUE recv, VALUE name) rb_exc_raise(exc); } +RBIMPL_ATTR_NONNULL((2)) static inline bool rb_typeddata_is_instance_of_inline(VALUE obj, const rb_data_type_t *data_type) { - return RB_TYPE_P(obj, T_DATA) && RTYPEDDATA_P(obj) && (RTYPEDDATA_TYPE(obj) == data_type); + return rbimpl_obj_typeddata_p(obj) && (RTYPEDDATA_TYPE(obj) == data_type); } typedef enum { From 56a6a21f28bd2b47bc96c58ae276b272933e7f62 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 29 Dec 2025 18:19:28 +0900 Subject: [PATCH 4/5] Return `NULL` in a `void *` function --- include/ruby/internal/core/rtypeddata.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 8952ba91810aac..1dd7397f7d335c 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -566,7 +566,7 @@ RTYPEDDATA_GET_DATA(VALUE obj) #if RUBY_DEBUG if (RB_UNLIKELY(!RB_TYPE_P(obj, RUBY_T_DATA))) { Check_Type(obj, RUBY_T_DATA); - RBIMPL_UNREACHABLE_RETURN(false); + RBIMPL_UNREACHABLE_RETURN(NULL); } #endif From 26088dcd4a1f1784ae24387ce6a98fcad48749c5 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 29 Dec 2025 18:41:58 +0900 Subject: [PATCH 5/5] [DOC] State that `rb_unexpected_type` is private --- include/ruby/internal/error.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/ruby/internal/error.h b/include/ruby/internal/error.h index 3ff885b2b151b7..5bf82bfe7d632e 100644 --- a/include/ruby/internal/error.h +++ b/include/ruby/internal/error.h @@ -421,11 +421,12 @@ void rb_readwrite_syserr_fail(enum rb_io_wait_readwrite waiting, int err, const RBIMPL_ATTR_COLD() RBIMPL_ATTR_NORETURN() /** + * @private + * * Fails with the given object's type incompatibility to the type. * - * It seems this function is visible from extension libraries only because - * RTYPEDDATA_TYPE() uses it on RUBY_DEBUG. So you can basically ignore it; - * use some other fine-grained method instead. + * This is an implementation detail of Check_Type. People don't use it + * directly. * * @param[in] self The object in question. * @param[in] t Expected type of the object.