Skip to content

lib/fs/readlink/: readlinknul(): Use ssize_t to simplify #1205

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 3, 2025

Conversation

alejandro-colomar
Copy link
Collaborator

@alejandro-colomar alejandro-colomar commented Feb 9, 2025

Consistently using a signed type allows us to avoid sign-mismatch diagnostics, while keeping the code simple. It feels weird to accept a ssize_t instead of a size_t, but it's a matter of getting used to it.

Another way to achieve this with a single 'len' variable and no casts would be to compare against SIZE_MAX, but that's less readable than -1. Or one could write a SIZE_C() macro a la UINT64_C(), and compare the size_t against SIZE_C(-1), but that's still suboptimal (regarding readability) compared to consistently using signed size types.

Fixes: b9d00b6 (2024-12-09; "lib/fs/readlink/readlinknul.h: readlinknul(): Silence warning")
Cc: @uecker
Cc: @rcseacord


I've also promoted -Wsign-compare diagnostics to errors in a second commit, since these are usually signs of deeply bogus code.


Long term, I'm considering switching many APIs from size_t to ssize_t. There's too much food in the plate for now, though. As long as -Werror=sign-compare doesn't trigger, we shouldn't need to worry too much. (In some cases, we might be silencing them with casts, but those are in old code, so we're not in a hurry to fix those.) :)


Revisions:

v2
  • Fix an old -Wsign-compare diagnostic, which is transformed into an error in commit 2.
  • Add ssizeof(), for use in the fix mentioned above.
$ git range-diff shadow/master gh/rln rln 
1:  d8cdef40 = 1:  d8cdef40 lib/fs/readlink/: readlinknul(): Use ssize_t to simplify
2:  fb8c417b = 2:  fb8c417b autogen.sh: Promote -Wsign-compare to an error
-:  -------- > 3:  6adae5fd lib/sizeof.h: ssizeof(): Add signed variant of sizeof
-:  -------- > 4:  4d2c9fe6 src/lastlog.c: Use ssizeof() to avoid a -Wsign-compare diagnostic
v3
  • Workaround annoying interface of mock(), which returns an unsigned integer.
$ git range-diff master gh/rln rln 
1:  2e47f4dd = 1:  2e47f4dd src/useradd.c: E_BAD_NAME: Use a different error code for bad login names
2:  d8cdef40 = 2:  d8cdef40 lib/fs/readlink/: readlinknul(): Use ssize_t to simplify
3:  fb8c417b = 3:  fb8c417b autogen.sh: Promote -Wsign-compare to an error
4:  6adae5fd = 4:  6adae5fd lib/sizeof.h: ssizeof(): Add signed variant of sizeof
5:  4d2c9fe6 = 5:  4d2c9fe6 src/lastlog.c: Use ssizeof() to avoid a -Wsign-compare diagnostic
-:  -------- > 6:  e08f476a tests/unit/test_xasprintf.c: Fix sign-mismatch diagnostic
v3b
  • Remove comment.
$ git range-diff shadow/master gh/rln rln 
1:  d8cdef40 = 1:  d8cdef40 lib/fs/readlink/: readlinknul(): Use ssize_t to simplify
2:  fb8c417b = 2:  fb8c417b autogen.sh: Promote -Wsign-compare to an error
3:  6adae5fd = 3:  6adae5fd lib/sizeof.h: ssizeof(): Add signed variant of sizeof
4:  4d2c9fe6 = 4:  4d2c9fe6 src/lastlog.c: Use ssizeof() to avoid a -Wsign-compare diagnostic
5:  e08f476a ! 5:  0de52cbc tests/unit/test_xasprintf.c: Fix sign-mismatch diagnostic
    @@ tests/unit/test_xasprintf.c
      #include "string/sprintf/xasprintf.h"
      
      
    -+// Signed version of mock()
     +#define smock()               _Generic(mock(), uintmax_t: (intmax_t) mock())
    -+
      #define assert_unreachable()  assert_true(0)
      
      #define XASPRINTF_CALLED  (-36)
v3c
$ git range-diff alx/master..gh/rln shadow/master..rln 
1:  d8cdef40 ! 1:  fae8c87d lib/fs/readlink/: readlinknul(): Use ssize_t to simplify
    @@ Commit message
         readability) compared to consistently using signed size types.
     
         Fixes: b9d00b64a19f (2024-12-09; "lib/fs/readlink/readlinknul.h: readlinknul(): Silence warning")
    +    Acked-by: Serge Hallyn <[email protected]>
         Cc: Martin Uecker <[email protected]>
         Cc: "Robert C. Seacord" <[email protected]>
         Signed-off-by: Alejandro Colomar <[email protected]>
2:  fb8c417b ! 2:  d26e5c30 autogen.sh: Promote -Wsign-compare to an error
    @@ Commit message
     
         It is usually a sign of deep errors.  We really want to avoid them.
     
    +    Acked-by: Serge Hallyn <[email protected]>
         Signed-off-by: Alejandro Colomar <[email protected]>
     
      ## autogen.sh ##
3:  6adae5fd ! 3:  eaf327c3 lib/sizeof.h: ssizeof(): Add signed variant of sizeof
    @@ Metadata
      ## Commit message ##
         lib/sizeof.h: ssizeof(): Add signed variant of sizeof
     
    +    Acked-by: Serge Hallyn <[email protected]>
         Signed-off-by: Alejandro Colomar <[email protected]>
     
      ## lib/sizeof.h ##
4:  4d2c9fe6 ! 4:  424d745b src/lastlog.c: Use ssizeof() to avoid a -Wsign-compare diagnostic
    @@ Metadata
      ## Commit message ##
         src/lastlog.c: Use ssizeof() to avoid a -Wsign-compare diagnostic
     
    +    Acked-by: Serge Hallyn <[email protected]>
         Signed-off-by: Alejandro Colomar <[email protected]>
     
      ## src/lastlog.c ##
5:  0de52cbc ! 5:  3859fb89 tests/unit/test_xasprintf.c: Fix sign-mismatch diagnostic
    @@ Commit message
         Add a signed wrapper around mock() which returns a signed integer.
         This makes it possible to compare the return value with literal -1.
     
    +    Acked-by: Serge Hallyn <[email protected]>
         Signed-off-by: Alejandro Colomar <[email protected]>
     
      ## tests/unit/test_xasprintf.c ##
v3d
  • Rebase
$ git range-diff master..gh/rln shadow/master..rln 
1:  fae8c87d = 1:  a2de55a9 lib/fs/readlink/: readlinknul(): Use ssize_t to simplify
2:  d26e5c30 = 2:  3372a423 autogen.sh: Promote -Wsign-compare to an error
3:  eaf327c3 = 3:  ef39c0da lib/sizeof.h: ssizeof(): Add signed variant of sizeof
4:  424d745b = 4:  788b0ec3 src/lastlog.c: Use ssizeof() to avoid a -Wsign-compare diagnostic
5:  3859fb89 = 5:  de0cc30e tests/unit/test_xasprintf.c: Fix sign-mismatch diagnostic
v3e
  • Rebase
$ git range-diff master..gh/rln shadow/master..rln 
1:  a2de55a9 = 1:  804a6587 lib/fs/readlink/: readlinknul(): Use ssize_t to simplify
2:  3372a423 = 2:  5ce552e0 autogen.sh: Promote -Wsign-compare to an error
3:  ef39c0da = 3:  81eb856f lib/sizeof.h: ssizeof(): Add signed variant of sizeof
4:  788b0ec3 = 4:  1aed4c86 src/lastlog.c: Use ssizeof() to avoid a -Wsign-compare diagnostic
5:  de0cc30e = 5:  ba8fdaa5 tests/unit/test_xasprintf.c: Fix sign-mismatch diagnostic
v3f
  • Rebase
$ git range-diff master..gh/rln shadow/master..rln 
1:  804a6587 = 1:  8aff42d7 lib/fs/readlink/: readlinknul(): Use ssize_t to simplify
2:  5ce552e0 = 2:  458fa80c autogen.sh: Promote -Wsign-compare to an error
3:  81eb856f = 3:  b6cc573c lib/sizeof.h: ssizeof(): Add signed variant of sizeof
4:  1aed4c86 = 4:  f135cfc2 src/lastlog.c: Use ssizeof() to avoid a -Wsign-compare diagnostic
5:  ba8fdaa5 = 5:  bcfca3f9 tests/unit/test_xasprintf.c: Fix sign-mismatch diagnostic

Copy link
Member

@hallyn hallyn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good to me, but I'm not 100% sure about the semantics of mock() (after skimming https://lwn.net/Articles/558106/), or _Generic for that matter.

@ikerexxe could you take a quick look at the last patch in this set?

@hallyn
Copy link
Member

hallyn commented Feb 15, 2025

(But obviously +1 from me)

@alejandro-colomar
Copy link
Collaborator Author

alejandro-colomar commented Feb 15, 2025

Overall looks good to me, but I'm not 100% sure about the semantics of mock() (after skimming https://lwn.net/Articles/558106/), or _Generic for that matter.

@ikerexxe could you take a quick look at the last patch in this set?

From what I've experimented with it, my mental model is that mock() returns whatever you pass in will_return() $2, whenever you arrive at the code in will_return() $1.

	// Trick: it will actually return the length, not 0.
	will_return(__wrap_vasprintf, 0);

so if we execute the above, within __wrap_vasprintf(), mock() will return 0.

and if we execute the following, mock() will return -1:

	will_return(__wrap_vasprintf, -1);

See the implementation of __wrap_vasprintf():

int
__wrap_vasprintf(char **restrict p, const char *restrict fmt, va_list ap)
{
	return mock() == -1 ? -1 : __real_vasprintf(p, fmt, ap);
}

which reads that value.

@alejandro-colomar
Copy link
Collaborator Author

alejandro-colomar commented Feb 15, 2025

Overall looks good to me, but I'm not 100% sure about the semantics of [...] _Generic for that matter.

I wrote a manual page for _Generic(3), if you need it.

https://kernel.org/pub/linux/docs/man-pages/book/man-pages-6.11.pdf#_Generic%283%29

And here goes an explanation of why I'm using it in this code:

mock() returns a uintmax_t, which is unsigned. I need a signed integer, so I need intmax_t. A cast would be enough:

#define smock() ((intmax_t) mock())

However, I wanted to make sure that the original type is uintmax_t, to make sure that my cast is valid. That's why I used _Generic(3).

#define smock()               _Generic(mock(), uintmax_t: (intmax_t) mock())

This controlling expression in this _Generic(3) is mock().
_Generic(3) makes sure that the type of the controlling expression, which is typeof(mock()), which is the return type of mock(), matches the only existing type selector. If there is no type selector that matches the type of the controlling expression, it is a "constraint violation" (that's an ISO C term meaning that compilation must fail).

So, if the return type of mock() doesn't match uintmax_t, it is a constraint violation.

One it is true, _Generic(3) evaluates the code under the type selector that matches, so it evaluates as (intmax_t) mock().


While I could have just tested that mock() returns a uintmax_t, and then continued with the version with just a cast, I thought it would be useful to keep the _Generic(3) there as a reminder that mock() returns a uintmax_t.

@alejandro-colomar
Copy link
Collaborator Author

alejandro-colomar commented Feb 15, 2025

(But obviously +1 from me)

Thanks! I took that as an Acked-by in v3c (somewhat less than a Reviewed-by, because you weren't 100% comfortable).

@alejandro-colomar alejandro-colomar force-pushed the rln branch 3 times, most recently from de0cc30 to ba8fdaa Compare February 16, 2025 22:47
@alejandro-colomar alejandro-colomar mentioned this pull request Feb 21, 2025
@alejandro-colomar
Copy link
Collaborator Author

alejandro-colomar commented Feb 25, 2025

Overall looks good to me, but I'm not 100% sure about the semantics of mock() (after skimming https://lwn.net/Articles/558106/), or _Generic for that matter.

@hallyn was the explanation about _Generic and mock() ok? Would you like me to document anything better? (Or change some code?)

Consistently using a signed type allows us to avoid sign-mismatch
diagnostics, while keeping the code simple.  It feels weird to
accept a ssize_t instead of a size_t, but it's a matter of getting
used to it.

Another way to achieve this with a single 'len' variable and no casts
would be to compare against SIZE_MAX, but that's less readable than -1.
Or one could write a SIZE_C() macro a la UINT64_C(), and compare the
size_t against SIZE_C(-1), but that's still suboptimal (regarding
readability) compared to consistently using signed size types.

Fixes: b9d00b6 (2024-12-09; "lib/fs/readlink/readlinknul.h: readlinknul(): Silence warning")
Acked-by: Serge Hallyn <[email protected]>
Cc: Martin Uecker <[email protected]>
Cc: "Robert C. Seacord" <[email protected]>
Signed-off-by: Alejandro Colomar <[email protected]>
It is usually a sign of deep errors.  We really want to avoid them.

Acked-by: Serge Hallyn <[email protected]>
Signed-off-by: Alejandro Colomar <[email protected]>
Acked-by: Serge Hallyn <[email protected]>
Signed-off-by: Alejandro Colomar <[email protected]>
Add a signed wrapper around mock() which returns a signed integer.
This makes it possible to compare the return value with literal -1.

Acked-by: Serge Hallyn <[email protected]>
Signed-off-by: Alejandro Colomar <[email protected]>
@alejandro-colomar
Copy link
Collaborator Author

Since there have been no more comments, I'll merge. We can continue clarifying the details of _Generic and mock() later if necessary.

@alejandro-colomar alejandro-colomar merged commit d2d89a8 into shadow-maint:master Mar 3, 2025
9 checks passed
@alejandro-colomar alejandro-colomar deleted the rln branch March 3, 2025 23:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants