diff mbox series

[babeltrace] Fix: bitfield: left shift undefinedbehavior

Message ID 20190507204039.10496-1-mathieu.desnoyers@efficios.com
State Superseded, archived
Headers show
Series [babeltrace] Fix: bitfield: left shift undefinedbehavior | expand

Commit Message

Mathieu Desnoyers May 7, 2019, 8:40 p.m. UTC
bitfield.h uses the left shift operator with a left operand which
may be negative. The C99 standard states that shifting a negative
value is undefined.

We also need to cast the result explicitly into the left hand
side type to deal with:

warning: large integer implicitly truncated to unsigned type [-Woverflow]

Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers at efficios.com>
---
 include/babeltrace/bitfield-internal.h | 61 ++++++++++++++++++----------------
 1 file changed, 33 insertions(+), 28 deletions(-)

Comments

Simon Marchi May 7, 2019, 10:13 p.m. UTC | #1
On 2019-05-07 4:40 p.m., Mathieu Desnoyers wrote:
> bitfield.h uses the left shift operator with a left operand which
> may be negative. The C99 standard states that shifting a negative
> value is undefined.

Please add a reference to the original problem that prompted you to
do this change in the commit message.  For example:

--- 8< ---

When building with -Wshift-negative-value, we get this gcc warning:

In file included from /home/smarchi/src/babeltrace/include/babeltrace/ctfser-internal.h:44:0,
                 from /home/smarchi/src/babeltrace/ctfser/ctfser.c:42:
/home/smarchi/src/babeltrace/include/babeltrace/ctfser-internal.h: In function ?bt_ctfser_write_unsigned_int?:
/home/smarchi/src/babeltrace/include/babeltrace/bitfield-internal.h:116:24: error: left shift of negative value [-Werror=shift-negative-value]
   mask = ~((~(type) 0) << (__start % ts));  \
                        ^
/home/smarchi/src/babeltrace/include/babeltrace/bitfield-internal.h:222:2: note: in expansion of macro ?_bt_bitfield_write_le?
  _bt_bitfield_write_le(ptr, type, _start, _length, _v)
  ^~~~~~~~~~~~~~~~~~~~~
/home/smarchi/src/babeltrace/include/babeltrace/ctfser-internal.h:418:3: note: in expansion of macro ?bt_bitfield_write_le?
   bt_bitfield_write_le(mmap_align_addr(ctfser->base_mma) +
   ^~~~~~~~~~~~~~~~~~~~


This boils down to the fact that the expression ~((uint8_t)0) has type
"signed int", which is used as an operand of the left shift.  This is due
to the integer promotion rules of C99 (6.3.3.1):

    If an int can represent all values of the original type, the value is
    converted to an int; otherwise, it is converted to an unsigned int.
    These are called the integer promotions. All other types are unchanged
    by the integer promotions.

--- >8 ---

> 
> We also need to cast the result explicitly into the left hand
> side type to deal with:
> 
> warning: large integer implicitly truncated to unsigned type [-Woverflow]
> 
> Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers at efficios.com>
> ---
>  include/babeltrace/bitfield-internal.h | 61 ++++++++++++++++++----------------
>  1 file changed, 33 insertions(+), 28 deletions(-)
> 
> diff --git a/include/babeltrace/bitfield-internal.h b/include/babeltrace/bitfield-internal.h
> index c5d5eccd..da56f089 100644
> --- a/include/babeltrace/bitfield-internal.h
> +++ b/include/babeltrace/bitfield-internal.h
> @@ -55,19 +55,24 @@
>  #define _bt_is_signed_type(type)	((type) -1 < (type) 0)
>  
>  /*
> - * NOTE: The cast to (uint64_t) below ensures that we're not casting a
> - * negative value, which is undefined in C. However, this limits the
> - * maximum type size of `type` and `v` to 64-bit. The
> - * _bt_check_max_64bit() is used to check that the users of this header
> - * do not use types with a size greater than 64-bit.
> + * NOTE: The unsigned cast ensures that we're not shifting a negative
> + * value, which is undefined in C. However, this limits the maximum
> + * type size of `type` and `v` to 64-bit. The _bt_check_max_64bit() is
> + * used to check that the users of this header do not use types with a
> + * size greater than 64-bit.
>   */

The comment explains "why" this macro is needed, but doesn't explain what
it does.  I would suggest starting the comment with:

Cast value `v` to an unsigned integer type of the size of type `type`.

>
>  #define _bt_unsigned_cast(type, v)					\
>  ({									\
> -	(sizeof(v) < sizeof(type)) ?					\
> -		((type) (v)) & ((type) (~(~(uint64_t) 0 << (sizeof(v) * CHAR_BIT)))) : \
> -		(type) (v);						\
> +	__builtin_types_compatible_p(type, int8_t) ? (uint8_t) (v) :	\
> +	__builtin_types_compatible_p(type, int16_t) ? (uint16_t) (v) :	\
> +	__builtin_types_compatible_p(type, int32_t) ? (uint32_t) (v) :	\
> +	__builtin_types_compatible_p(type, int64_t) ? (uint64_t) (v) :	\
> +	(type) (v);							\
>  })

What should we do in the "else" case.  The macro claims to cast to an unsigned
type, but in the case where the "else" branch would be taken, the value is cast
to the original type (possible signed).  I think this can be very misleading.

Knowing that all of this is supposed to get resolved at compiled time, would
there be a way to generate compilation error in the "else" branch?  If we ever
reach it, it means we need to add something here.

>  
> +/* Unsigned bitwise complement. */
> +#define _bt_bitwise_not(type, v)	_bt_unsigned_cast(type, ~(type) v)

I was trying to put words on what this macro does, to document it a bit better,
but then realized it iss always used with v = 0.  I don't know if you want to
simplify and remove the v parameter to hard-code 0?  In the end, all you want is
something that yields a value of the same size as type, but unsigned, and filled
with ones.

Since you said that you would change the code, I'll wait for the next version to
look at the hairy details.

Simon
Mathieu Desnoyers May 8, 2019, 2:37 p.m. UTC | #2
----- On May 7, 2019, at 6:13 PM, Simon Marchi simark at simark.ca wrote:

> On 2019-05-07 4:40 p.m., Mathieu Desnoyers wrote:
>> bitfield.h uses the left shift operator with a left operand which
>> may be negative. The C99 standard states that shifting a negative
>> value is undefined.

Hi Simon,

Thanks a ton for your input! I've taken care of all your comments and
will post an updated patch for babeltrace.

Once we all agree on the content, I'll work on porting this to
lttng-ust, lttng-modules, and barectf.

Let's keep in mind that bitfield.h needs to stay ANSI-C compatible
as much as possible so the barectf implementation don't diverge too
much.

Thanks,

Mathieu


> 
> Please add a reference to the original problem that prompted you to
> do this change in the commit message.  For example:
> 
> --- 8< ---
> 
> When building with -Wshift-negative-value, we get this gcc warning:
> 
> In file included from
> /home/smarchi/src/babeltrace/include/babeltrace/ctfser-internal.h:44:0,
>                 from /home/smarchi/src/babeltrace/ctfser/ctfser.c:42:
> /home/smarchi/src/babeltrace/include/babeltrace/ctfser-internal.h: In function
> ?bt_ctfser_write_unsigned_int?:
> /home/smarchi/src/babeltrace/include/babeltrace/bitfield-internal.h:116:24:
> error: left shift of negative value [-Werror=shift-negative-value]
>   mask = ~((~(type) 0) << (__start % ts));  \
>                        ^
> /home/smarchi/src/babeltrace/include/babeltrace/bitfield-internal.h:222:2: note:
> in expansion of macro ?_bt_bitfield_write_le?
>  _bt_bitfield_write_le(ptr, type, _start, _length, _v)
>  ^~~~~~~~~~~~~~~~~~~~~
> /home/smarchi/src/babeltrace/include/babeltrace/ctfser-internal.h:418:3: note:
> in expansion of macro ?bt_bitfield_write_le?
>   bt_bitfield_write_le(mmap_align_addr(ctfser->base_mma) +
>   ^~~~~~~~~~~~~~~~~~~~
> 
> 
> This boils down to the fact that the expression ~((uint8_t)0) has type
> "signed int", which is used as an operand of the left shift.  This is due
> to the integer promotion rules of C99 (6.3.3.1):
> 
>    If an int can represent all values of the original type, the value is
>    converted to an int; otherwise, it is converted to an unsigned int.
>    These are called the integer promotions. All other types are unchanged
>    by the integer promotions.
> 
> --- >8 ---
> 
>> 
>> We also need to cast the result explicitly into the left hand
>> side type to deal with:
>> 
>> warning: large integer implicitly truncated to unsigned type [-Woverflow]
>> 
>> Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers at efficios.com>
>> ---
>>  include/babeltrace/bitfield-internal.h | 61 ++++++++++++++++++----------------
>>  1 file changed, 33 insertions(+), 28 deletions(-)
>> 
>> diff --git a/include/babeltrace/bitfield-internal.h
>> b/include/babeltrace/bitfield-internal.h
>> index c5d5eccd..da56f089 100644
>> --- a/include/babeltrace/bitfield-internal.h
>> +++ b/include/babeltrace/bitfield-internal.h
>> @@ -55,19 +55,24 @@
>>  #define _bt_is_signed_type(type)	((type) -1 < (type) 0)
>>  
>>  /*
>> - * NOTE: The cast to (uint64_t) below ensures that we're not casting a
>> - * negative value, which is undefined in C. However, this limits the
>> - * maximum type size of `type` and `v` to 64-bit. The
>> - * _bt_check_max_64bit() is used to check that the users of this header
>> - * do not use types with a size greater than 64-bit.
>> + * NOTE: The unsigned cast ensures that we're not shifting a negative
>> + * value, which is undefined in C. However, this limits the maximum
>> + * type size of `type` and `v` to 64-bit. The _bt_check_max_64bit() is
>> + * used to check that the users of this header do not use types with a
>> + * size greater than 64-bit.
>>   */
> 
> The comment explains "why" this macro is needed, but doesn't explain what
> it does.  I would suggest starting the comment with:
> 
> Cast value `v` to an unsigned integer type of the size of type `type`.
> 
>>
>>  #define _bt_unsigned_cast(type, v)					\
>>  ({									\
>> -	(sizeof(v) < sizeof(type)) ?					\
>> -		((type) (v)) & ((type) (~(~(uint64_t) 0 << (sizeof(v) * CHAR_BIT)))) : \
>> -		(type) (v);						\
>> +	__builtin_types_compatible_p(type, int8_t) ? (uint8_t) (v) :	\
>> +	__builtin_types_compatible_p(type, int16_t) ? (uint16_t) (v) :	\
>> +	__builtin_types_compatible_p(type, int32_t) ? (uint32_t) (v) :	\
>> +	__builtin_types_compatible_p(type, int64_t) ? (uint64_t) (v) :	\
>> +	(type) (v);							\
>>  })
> 
> What should we do in the "else" case.  The macro claims to cast to an unsigned
> type, but in the case where the "else" branch would be taken, the value is cast
> to the original type (possible signed).  I think this can be very misleading.
> 
> Knowing that all of this is supposed to get resolved at compiled time, would
> there be a way to generate compilation error in the "else" branch?  If we ever
> reach it, it means we need to add something here.
> 
>>  
>> +/* Unsigned bitwise complement. */
>> +#define _bt_bitwise_not(type, v)	_bt_unsigned_cast(type, ~(type) v)
> 
> I was trying to put words on what this macro does, to document it a bit better,
> but then realized it iss always used with v = 0.  I don't know if you want to
> simplify and remove the v parameter to hard-code 0?  In the end, all you want is
> something that yields a value of the same size as type, but unsigned, and filled
> with ones.
> 
> Since you said that you would change the code, I'll wait for the next version to
> look at the hairy details.
> 
> Simon
diff mbox series

Patch

diff --git a/include/babeltrace/bitfield-internal.h b/include/babeltrace/bitfield-internal.h
index c5d5eccd..da56f089 100644
--- a/include/babeltrace/bitfield-internal.h
+++ b/include/babeltrace/bitfield-internal.h
@@ -55,19 +55,24 @@ 
 #define _bt_is_signed_type(type)	((type) -1 < (type) 0)
 
 /*
- * NOTE: The cast to (uint64_t) below ensures that we're not casting a
- * negative value, which is undefined in C. However, this limits the
- * maximum type size of `type` and `v` to 64-bit. The
- * _bt_check_max_64bit() is used to check that the users of this header
- * do not use types with a size greater than 64-bit.
+ * NOTE: The unsigned cast ensures that we're not shifting a negative
+ * value, which is undefined in C. However, this limits the maximum
+ * type size of `type` and `v` to 64-bit. The _bt_check_max_64bit() is
+ * used to check that the users of this header do not use types with a
+ * size greater than 64-bit.
  */
 #define _bt_unsigned_cast(type, v)					\
 ({									\
-	(sizeof(v) < sizeof(type)) ?					\
-		((type) (v)) & ((type) (~(~(uint64_t) 0 << (sizeof(v) * CHAR_BIT)))) : \
-		(type) (v);						\
+	__builtin_types_compatible_p(type, int8_t) ? (uint8_t) (v) :	\
+	__builtin_types_compatible_p(type, int16_t) ? (uint16_t) (v) :	\
+	__builtin_types_compatible_p(type, int32_t) ? (uint32_t) (v) :	\
+	__builtin_types_compatible_p(type, int64_t) ? (uint64_t) (v) :	\
+	(type) (v);							\
 })
 
+/* Unsigned bitwise complement. */
+#define _bt_bitwise_not(type, v)	_bt_unsigned_cast(type, ~(type) v)
+
 #define _bt_check_max_64bit(type)					\
 	char _max_64bit_assertion[sizeof(type) <= sizeof(uint64_t) ? 1 : -1] __attribute__((unused))
 
@@ -108,15 +113,15 @@  do {									\
 									\
 	/* Trim v high bits */						\
 	if (__length < sizeof(__v) * CHAR_BIT)				\
-		__v &= ~((~(typeof(__v)) 0) << __length);		\
+		__v &= (__typeof__(__v)) ~(_bt_bitwise_not(typeof(__v), 0) << __length); \
 									\
 	/* We can now append v with a simple "or", shift it piece-wise */ \
 	this_unit = start_unit;						\
 	if (start_unit == end_unit - 1) {				\
-		mask = ~((~(type) 0) << (__start % ts));		\
+		mask = (__typeof__(mask)) ~(_bt_bitwise_not(type, 0) << (__start % ts)); \
 		if (end % ts)						\
-			mask |= (~(type) 0) << (end % ts);		\
-		cmask = (type) __v << (__start % ts);			\
+			mask |= (__typeof__(mask)) (_bt_bitwise_not(type, 0) << (end % ts)); \
+		cmask = (__typeof__(cmask)) (_bt_unsigned_cast(type, __v) << (__start % ts)); \
 		cmask &= ~mask;						\
 		__ptr[this_unit] &= mask;				\
 		__ptr[this_unit] |= cmask;				\
@@ -124,8 +129,8 @@  do {									\
 	}								\
 	if (__start % ts) {						\
 		cshift = __start % ts;					\
-		mask = ~((~(type) 0) << cshift);			\
-		cmask = (type) __v << cshift;				\
+		mask = (__typeof__(mask)) ~(_bt_bitwise_not(type, 0) << cshift); \
+		cmask = (__typeof__(cmask)) (_bt_unsigned_cast(type, __v) << cshift); \
 		cmask &= ~mask;						\
 		__ptr[this_unit] &= mask;				\
 		__ptr[this_unit] |= cmask;				\
@@ -139,7 +144,7 @@  do {									\
 		__start += ts;						\
 	}								\
 	if (end % ts) {							\
-		mask = (~(type) 0) << (end % ts);			\
+		mask = (__typeof__(mask)) (_bt_bitwise_not(type, 0) << (end % ts)); \
 		cmask = (type) __v;					\
 		cmask &= ~mask;						\
 		__ptr[this_unit] &= mask;				\
@@ -167,15 +172,15 @@  do {									\
 									\
 	/* Trim v high bits */						\
 	if (__length < sizeof(__v) * CHAR_BIT)				\
-		__v &= ~((~(typeof(__v)) 0) << __length);		\
+		__v &= (__typeof__(__v)) ~(_bt_bitwise_not(typeof(__v), 0) << __length); \
 									\
 	/* We can now append v with a simple "or", shift it piece-wise */ \
 	this_unit = end_unit - 1;					\
 	if (start_unit == end_unit - 1) {				\
-		mask = ~((~(type) 0) << ((ts - (end % ts)) % ts));	\
+		mask = (__typeof__(mask)) ~(_bt_bitwise_not(type, 0) << ((ts - (end % ts)) % ts)); \
 		if (__start % ts)					\
-			mask |= (~((type) 0)) << (ts - (__start % ts));	\
-		cmask = (type) __v << ((ts - (end % ts)) % ts);		\
+			mask |= (__typeof__(mask)) (_bt_bitwise_not(type, 0) << (ts - (__start % ts))); \
+		cmask = (__typeof__(cmask)) (_bt_unsigned_cast(type, __v) << ((ts - (end % ts)) % ts)); \
 		cmask &= ~mask;						\
 		__ptr[this_unit] &= mask;				\
 		__ptr[this_unit] |= cmask;				\
@@ -183,8 +188,8 @@  do {									\
 	}								\
 	if (end % ts) {							\
 		cshift = end % ts;					\
-		mask = ~((~(type) 0) << (ts - cshift));			\
-		cmask = (type) __v << (ts - cshift);			\
+		mask = (__typeof__(mask)) ~(_bt_bitwise_not(type, 0) << (ts - cshift));	\
+		cmask = (__typeof__(cmask)) (_bt_unsigned_cast(type, __v) << (ts - cshift)); \
 		cmask &= ~mask;						\
 		__ptr[this_unit] &= mask;				\
 		__ptr[this_unit] |= cmask;				\
@@ -198,7 +203,7 @@  do {									\
 		end -= ts;						\
 	}								\
 	if (__start % ts) {						\
-		mask = (~(type) 0) << (ts - (__start % ts));		\
+		mask = (__typeof__(mask)) (_bt_bitwise_not(type, 0) << (ts - (__start % ts))); \
 		cmask = (type) __v;					\
 		cmask &= ~mask;						\
 		__ptr[this_unit] &= mask;				\
@@ -275,7 +280,7 @@  do {									\
 		cmask = __ptr[this_unit];				\
 		cmask >>= (__start % ts);				\
 		if ((end - __start) % ts) {				\
-			mask = ~((~(type) 0) << (end - __start));	\
+			mask = (__typeof__(mask)) ~(_bt_bitwise_not(type, 0) << (end - __start)); \
 			cmask &= mask;					\
 		}							\
 		__v = _bt_piecewise_lshift(__v, end - __start);		\
@@ -285,7 +290,7 @@  do {									\
 	}								\
 	if (end % ts) {							\
 		cshift = end % ts;					\
-		mask = ~((~(type) 0) << cshift);			\
+		mask = (__typeof__(mask)) ~(_bt_bitwise_not(type, 0) << cshift); \
 		cmask = __ptr[this_unit];				\
 		cmask &= mask;						\
 		__v = _bt_piecewise_lshift(__v, cshift);		\
@@ -299,7 +304,7 @@  do {									\
 		end -= ts;						\
 	}								\
 	if (__start % ts) {						\
-		mask = ~((~(type) 0) << (ts - (__start % ts)));		\
+		mask = (__typeof__(mask)) ~(_bt_bitwise_not(type, 0) << (ts - (__start % ts))); \
 		cmask = __ptr[this_unit];				\
 		cmask >>= (__start % ts);				\
 		cmask &= mask;						\
@@ -346,7 +351,7 @@  do {									\
 		cmask = __ptr[this_unit];				\
 		cmask >>= (ts - (end % ts)) % ts;			\
 		if ((end - __start) % ts) {				\
-			mask = ~((~(type) 0) << (end - __start));	\
+			mask = (__typeof__(mask)) ~(_bt_bitwise_not(type, 0) << (end - __start)); \
 			cmask &= mask;					\
 		}							\
 		__v = _bt_piecewise_lshift(__v, end - __start);		\
@@ -356,7 +361,7 @@  do {									\
 	}								\
 	if (__start % ts) {						\
 		cshift = __start % ts;					\
-		mask = ~((~(type) 0) << (ts - cshift));			\
+		mask = (__typeof__(mask)) ~(_bt_bitwise_not(type, 0) << (ts - cshift));	\
 		cmask = __ptr[this_unit];				\
 		cmask &= mask;						\
 		__v = _bt_piecewise_lshift(__v, ts - cshift);		\
@@ -370,7 +375,7 @@  do {									\
 		__start += ts;						\
 	}								\
 	if (end % ts) {							\
-		mask = ~((~(type) 0) << (end % ts));			\
+		mask = (__typeof__(mask)) ~(_bt_bitwise_not(type, 0) << (end % ts)); \
 		cmask = __ptr[this_unit];				\
 		cmask >>= ts - (end % ts);				\
 		cmask &= mask;						\