linux-trace-devel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] libtraceeval: Use trick to force static array usage where needed
@ 2023-10-04 22:52 Steven Rostedt
  2023-10-05 21:25 ` Ross Zwisler
  0 siblings, 1 reply; 3+ messages in thread
From: Steven Rostedt @ 2023-10-04 22:52 UTC (permalink / raw)
  To: Linux Trace Devel; +Cc: Ross Zwisler, Stevie Alvarez

From: "Steven Rostedt (Google)" <rostedt@goodmis.org>

Some functions use the TRACEEVAL_ARRAY_SIZE() macro to determine the size
of the array passed to it. But this will not work if the developer uses a
pointer to the array. gcc may give a warning, but it will still happily
compile it leaving the developer wondering why their code does not work.

Use a little trick that checks and tests if the array is static, and will
fail the build if it is not.

 #define TRACEEVAL_ARRAY_SIZE(data)					\
	((sizeof(data) / sizeof((data)[0])) +				\
	(int)(sizeof(struct {						\
		int:(-!!(__builtin_types_compatible_p(typeof(data),	\
						      typeof(&((data)[0]))))); \
			})))

Going backwards to explain the above, we have:

  __builtin_types_compatible_p(typeof(data), typeof(&((data)[0]))

Which is a gcc/clang compiler directive that returns true if the two
pointers are compatible [ (a, b) where a = b is valid ]. For a static
array, we would have

 struct traceeval_data data[5]

Where typeof(data) is "struct traceeval_data []" and the type of
 &data[0] is a pointer to "struct traceeval_data", and the above
would return false (zero) [ data = &data[0] is invalid ].

For pointers:

 struct traceeval_data *data;

Then type of data is the same as &data[0] and it would return true (1).
[ data = &data[0] is valid ]

Now we have a structure defined:

  struct {
    int: (-!!(<result>));
  }

Which if <result> of the __builtin_types_compatible_p() returned false
(zero), then it would be:

  struct {
    int: 0; // structure with int size of 0 bits
 }

Which is perfectly valid. But if <result> returned true (as it would if it
was a pointer and not a static array), then it would be:

  struct {
     int: -1;  // structure with int size of -1 bits!
  }

Which is not valid to compile, and the build will fail.

But in order to make sure this is in the code that uses
TRACEEVAL_ARRAY_SIZE(), it needs to be part of the computation. To do
that, as "struct { int:0; }" is of size zero, we can simply add a sizeof()
around it, and attach the above with an addition "+".

 ... + sizeof((int)(sizeof(struct { int:0;}))) is the same as:

 ... + 0

Now with this logic, if a pointer is passed to something like
traceeval_init(), it will fail to build, and not cause hours of scratching
head debugging for the developer at runtime.

Of course, the developer will likely now scratch their head on why it
doesn't build, but that's because they didn't RTFM!

Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
---
 include/traceeval.h | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/include/traceeval.h b/include/traceeval.h
index 4cc5eb6ef3de..6c6e09c53129 100644
--- a/include/traceeval.h
+++ b/include/traceeval.h
@@ -17,7 +17,12 @@
 /* Field name/descriptor for number of hits */
 #define TRACEEVAL_VAL_HITS ((const char *)(-1UL))
 
-#define TRACEEVAL_ARRAY_SIZE(data)	(sizeof(data) / sizeof((data)[0]))
+#define TRACEEVAL_ARRAY_SIZE(data)					\
+	((sizeof(data) / sizeof((data)[0])) +				\
+	(int)(sizeof(struct {						\
+		int:(-!!(__builtin_types_compatible_p(typeof(data),	\
+						      typeof(&((data)[0]))))); \
+			})))
 
 /* Data type distinguishers */
 enum traceeval_data_type {
-- 
2.40.1


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH] libtraceeval: Use trick to force static array usage where needed
  2023-10-04 22:52 [PATCH] libtraceeval: Use trick to force static array usage where needed Steven Rostedt
@ 2023-10-05 21:25 ` Ross Zwisler
  2023-10-05 21:59   ` Steven Rostedt
  0 siblings, 1 reply; 3+ messages in thread
From: Ross Zwisler @ 2023-10-05 21:25 UTC (permalink / raw)
  To: Steven Rostedt; +Cc: Linux Trace Devel, Stevie Alvarez

On Wed, Oct 04, 2023 at 06:52:25PM -0400, Steven Rostedt wrote:
> From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
> 
> Some functions use the TRACEEVAL_ARRAY_SIZE() macro to determine the size
> of the array passed to it. But this will not work if the developer uses a
> pointer to the array. gcc may give a warning, but it will still happily
> compile it leaving the developer wondering why their code does not work.
> 
> Use a little trick that checks and tests if the array is static, and will
> fail the build if it is not.
> 
>  #define TRACEEVAL_ARRAY_SIZE(data)					\
> 	((sizeof(data) / sizeof((data)[0])) +				\
> 	(int)(sizeof(struct {						\
> 		int:(-!!(__builtin_types_compatible_p(typeof(data),	\
> 						      typeof(&((data)[0]))))); \
> 			})))
> 
> Going backwards to explain the above, we have:
> 
>   __builtin_types_compatible_p(typeof(data), typeof(&((data)[0]))
> 
> Which is a gcc/clang compiler directive that returns true if the two
> pointers are compatible [ (a, b) where a = b is valid ]. For a static
> array, we would have
> 
>  struct traceeval_data data[5]
> 
> Where typeof(data) is "struct traceeval_data []" and the type of
>  &data[0] is a pointer to "struct traceeval_data", and the above
> would return false (zero) [ data = &data[0] is invalid ].
> 
> For pointers:
> 
>  struct traceeval_data *data;
> 
> Then type of data is the same as &data[0] and it would return true (1).
> [ data = &data[0] is valid ]
> 
> Now we have a structure defined:
> 
>   struct {
>     int: (-!!(<result>));
>   }
> 
> Which if <result> of the __builtin_types_compatible_p() returned false
> (zero), then it would be:
> 
>   struct {
>     int: 0; // structure with int size of 0 bits
>  }
> 
> Which is perfectly valid. But if <result> returned true (as it would if it
> was a pointer and not a static array), then it would be:
> 
>   struct {
>      int: -1;  // structure with int size of -1 bits!
>   }
> 
> Which is not valid to compile, and the build will fail.
> 
> But in order to make sure this is in the code that uses
> TRACEEVAL_ARRAY_SIZE(), it needs to be part of the computation. To do
> that, as "struct { int:0; }" is of size zero, we can simply add a sizeof()
> around it, and attach the above with an addition "+".
> 
>  ... + sizeof((int)(sizeof(struct { int:0;}))) is the same as:
> 
>  ... + 0
> 
> Now with this logic, if a pointer is passed to something like
> traceeval_init(), it will fail to build, and not cause hours of scratching
> head debugging for the developer at runtime.
> 
> Of course, the developer will likely now scratch their head on why it
> doesn't build, but that's because they didn't RTFM!
> 
> Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>

That's some scary deep magic.  :)

We may want to add a comment above this to briefly explain, i.e. "This macro
will fail to build if 'data' is a pointer and not a static array" and refer
them to the commit history?  That way they have some way to proceed if/when
their build fails.

Otherwise looks good:
Reviewed-by: Ross Zwisler <zwisler@google.com>

> ---
>  include/traceeval.h | 7 ++++++-
>  1 file changed, 6 insertions(+), 1 deletion(-)
> 
> diff --git a/include/traceeval.h b/include/traceeval.h
> index 4cc5eb6ef3de..6c6e09c53129 100644
> --- a/include/traceeval.h
> +++ b/include/traceeval.h
> @@ -17,7 +17,12 @@
>  /* Field name/descriptor for number of hits */
>  #define TRACEEVAL_VAL_HITS ((const char *)(-1UL))
>  
> -#define TRACEEVAL_ARRAY_SIZE(data)	(sizeof(data) / sizeof((data)[0]))
> +#define TRACEEVAL_ARRAY_SIZE(data)					\
> +	((sizeof(data) / sizeof((data)[0])) +				\
> +	(int)(sizeof(struct {						\
> +		int:(-!!(__builtin_types_compatible_p(typeof(data),	\
> +						      typeof(&((data)[0]))))); \
> +			})))
>  
>  /* Data type distinguishers */
>  enum traceeval_data_type {
> -- 
> 2.40.1
> 

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] libtraceeval: Use trick to force static array usage where needed
  2023-10-05 21:25 ` Ross Zwisler
@ 2023-10-05 21:59   ` Steven Rostedt
  0 siblings, 0 replies; 3+ messages in thread
From: Steven Rostedt @ 2023-10-05 21:59 UTC (permalink / raw)
  To: Ross Zwisler; +Cc: Linux Trace Devel, Stevie Alvarez

On Thu, 5 Oct 2023 15:25:21 -0600
Ross Zwisler <zwisler@google.com> wrote:

> That's some scary deep magic.  :)

Yeah it is. Hence the detailed change log, and not just:

  "The macro prevents static traceeval_data *keys from being used"

> 
> We may want to add a comment above this to briefly explain, i.e. "This macro
> will fail to build if 'data' is a pointer and not a static array" and refer
> them to the commit history?  That way they have some way to proceed if/when
> their build fails.

Well, I don't think they need to understand how the macro work. But just
why it failed. Even though new compilers also warn about why it doesn't
work, I can still add:

/*
 * If your compile failed due to the below macro, it is likely you used
 * a pointer to struct traceeval_data, and not a static array.
 *
 * That is:
 *
 *    struct traceeval_data *keys;
 *
 * and not:
 *
 *    struct traceeval_data keys[] = { ... };
 */

I'll send a v2

> 
> Otherwise looks good:
> Reviewed-by: Ross Zwisler <zwisler@google.com>

Thanks!

-- Steve


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2023-10-05 21:58 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-10-04 22:52 [PATCH] libtraceeval: Use trick to force static array usage where needed Steven Rostedt
2023-10-05 21:25 ` Ross Zwisler
2023-10-05 21:59   ` Steven Rostedt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).