Skip to content

Conversation

@alejandro-colomar
Copy link
Collaborator

@alejandro-colomar alejandro-colomar commented Jul 16, 2025

See also: #1292 (comment)

Cc: @Karlson2k


Revisions:

v2
  • Remove n parameter in MEMDUP(). I think we won't need it, as it will likely be 1 everywhere we might want to duplicate memory. The use of this API seems to be duplicating a pointer to structure that uses static storage in non-reentrant functions.
  • malloc(3) doesn't need the ?: 1 trick. That's just for realloc(3).
$ git rd 
1:  67923ed0 ! 1:  eaf1e24f lib/string/strdup/: memdup(), MEMDUP(): Add APIs
    @@ lib/string/strdup/memdup.h (new)
     +#include "attr.h"
     +
     +
    -+#define MEMDUP(p, n, T)  _Generic(p, T *: (T *) memdup(p, n * sizeof(T)))
    ++#define MEMDUP(p, T)  _Generic(p, T *: (T *) memdup(p, sizeof(T)))
     +
     +
     +ATTR_MALLOC(free)
    @@ lib/string/strdup/memdup.h (new)
     +{
     +  void  *new;
     +
    -+  new = malloc(size ?: 1);
    ++  new = malloc(size);
     +  if (new == NULL)
     +          return NULL;
     +
2:  7bdb4e11 = 2:  62e91ee4 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  d9925423 ! 3:  5239d2bb lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
    @@ lib/utmp.c: get_current_utmp(void)
     -                  memcpy(ret, ut, sizeof (*ret));
     -  }
     +  if (NULL != ut)
    -+          ut = MEMDUP(ut, 1, struct utmpx);
    ++          ut = MEMDUP(ut, struct utmpx);
      
        endutxent();
      
v2b
  • Rebase
$ git rd 
1:  eaf1e24f = 1:  894bbbf8 lib/string/strdup/: memdup(), MEMDUP(): Add APIs
2:  62e91ee4 ! 2:  3775e2a3 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
    @@ lib/utmp.c
      #include "alloc/x/xcalloc.h"
     -#include "alloc/x/xmalloc.h"
      #include "sizeof.h"
    + #include "string/strchr/strnul.h"
      #include "string/strcmp/streq.h"
    - #include "string/strcmp/strprefix.h"
     @@ lib/utmp.c: get_current_utmp(void)
        }
      
3:  5239d2bb ! 3:  e6e4c808 lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
    @@ lib/utmp.c
     -#include "alloc/malloc.h"
      #include "alloc/x/xcalloc.h"
      #include "sizeof.h"
    - #include "string/strcmp/streq.h"
    + #include "string/strchr/strnul.h"
    +@@
      #include "string/strcmp/strprefix.h"
      #include "string/strcpy/strncpy.h"
      #include "string/strcpy/strtcpy.h"
v3
  • Rebase
$ git range-diff 894bbbf8add6^..gh/memdup shadow/master..memdup 
1:  894bbbf8 = 1:  f8276a62 lib/string/strdup/: memdup(), MEMDUP(): Add APIs
2:  3775e2a3 ! 2:  fb5f9fc9 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
    @@ lib/utmp.c
      #include "sizeof.h"
      #include "string/strchr/strnul.h"
      #include "string/strcmp/streq.h"
    -@@ lib/utmp.c: get_current_utmp(void)
    -   }
    - 
    +@@ lib/utmp.c: get_current_utmp(pid_t main_pid)
        if (NULL != ut) {
    --          ret = XMALLOC(1, struct utmpx);
    --          memcpy (ret, ut, sizeof (*ret));
    -+          ret = MALLOC(1, struct utmpx);
    -+          if (ret != NULL)
    -+                  memcpy(ret, ut, sizeof (*ret));
    +           struct utmpx  *ut_copy;
    + 
    +-          ut_copy = XMALLOC(1, struct utmpx);
    +-          memcpy(ut_copy, ut, sizeof(*ut));
    ++          ut_copy = MALLOC(1, struct utmpx);
    ++          if (ut_copy != NULL)
    ++                  memcpy(ut_copy, ut, sizeof(*ut));
    +           ut = ut_copy;
        }
      
    -   endutxent();
3:  e6e4c808 ! 3:  903035d4 lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
    @@ lib/utmp.c
      #include "string/strdup/xstrdup.h"
      #include "string/strdup/xstrndup.h"
      
    -@@ lib/utmp.c: static /*@null@*/ /*@only@*/struct utmpx *
    - get_current_utmp(void)
    - {
    -   struct utmpx  *ut;
    --  struct utmpx  *ret = NULL;
    - 
    -   setutxent();
    - 
    -@@ lib/utmp.c: get_current_utmp(void)
    +@@ lib/utmp.c: get_current_utmp(pid_t main_pid)
                }
        }
      
     -  if (NULL != ut) {
    --          ret = MALLOC(1, struct utmpx);
    --          if (ret != NULL)
    --                  memcpy(ret, ut, sizeof (*ret));
    +-          struct utmpx  *ut_copy;
    +-
    +-          ut_copy = MALLOC(1, struct utmpx);
    +-          if (ut_copy != NULL)
    +-                  memcpy(ut_copy, ut, sizeof(*ut));
    +-          ut = ut_copy;
     -  }
     +  if (NULL != ut)
     +          ut = MEMDUP(ut, struct utmpx);
      
        endutxent();
      
    --  return ret;
    -+  return ut;
    - }
    - 
    - 
v4
  • Rebase
$ git rd 
1:  f8276a62 = 1:  af04fb6d lib/string/strdup/: memdup(), MEMDUP(): Add APIs
2:  fb5f9fc9 ! 2:  9fe1fa3a lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
    @@ lib/utmp.c
     +#include "alloc/malloc.h"
      #include "alloc/x/xcalloc.h"
     -#include "alloc/x/xmalloc.h"
    + #include "attr.h"
      #include "sizeof.h"
      #include "string/strchr/strnul.h"
    - #include "string/strcmp/streq.h"
     @@ lib/utmp.c: get_current_utmp(pid_t main_pid)
        if (NULL != ut) {
                struct utmpx  *ut_copy;
3:  903035d4 ! 3:  ab7bb521 lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
    @@ lib/utmp.c
      
     -#include "alloc/malloc.h"
      #include "alloc/x/xcalloc.h"
    + #include "attr.h"
      #include "sizeof.h"
    - #include "string/strchr/strnul.h"
     @@
      #include "string/strcmp/strprefix.h"
      #include "string/strcpy/strncpy.h"
    @@ lib/utmp.c
      #include "string/strdup/xstrndup.h"
      
     @@ lib/utmp.c: get_current_utmp(pid_t main_pid)
    -           }
    -   }
    +   if (NULL == ut)
    +           ut = ut_by_pid ?: ut_by_line;
      
     -  if (NULL != ut) {
     -          struct utmpx  *ut_copy;
    @@ lib/utmp.c: get_current_utmp(pid_t main_pid)
     +  if (NULL != ut)
     +          ut = MEMDUP(ut, struct utmpx);
      
    -   endutxent();
    - 
    +   free(ut_by_line);
    +   free(ut_by_pid);
v4b
  • Rebase
$ git rd 
1:  af04fb6d = 1:  a1ebec8c lib/string/strdup/: memdup(), MEMDUP(): Add APIs
2:  9fe1fa3a = 2:  5fc012b4 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  ab7bb521 = 3:  06c665ad lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
v4c
  • Rebase
$ git rd 
1:  a1ebec8c = 1:  895034b7 lib/string/strdup/: memdup(), MEMDUP(): Add APIs
2:  5fc012b4 = 2:  19e196c8 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  06c665ad = 3:  1b732dbc lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
v4d
  • Rebase
$ git rd 
1:  895034b7 ! 1:  56500038 lib/string/strdup/: memdup(), MEMDUP(): Add APIs
    @@ lib/Makefile.am: libshadow_la_SOURCES = \
        string/strcpy/strtcpy.h \
     +  string/strdup/memdup.c \
     +  string/strdup/memdup.h \
    +   string/strdup/strdup.c \
    +   string/strdup/strdup.h \
        string/strdup/strndupa.c \
    -   string/strdup/strndupa.h \
    -   string/strdup/xstrdup.c \
     
      ## lib/string/strdup/memdup.c (new) ##
     @@
2:  19e196c8 ! 2:  33cba471 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
    @@ Commit message
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
      ## lib/utmp.c ##
    -@@
    - #include <string.h>
    - #include <fcntl.h>
    - 
    -+#include "alloc/malloc.h"
    - #include "alloc/x/xcalloc.h"
    --#include "alloc/x/xmalloc.h"
    - #include "attr.h"
    - #include "sizeof.h"
    - #include "string/strchr/strnul.h"
     @@ lib/utmp.c: get_current_utmp(pid_t main_pid)
        if (NULL != ut) {
                struct utmpx  *ut_copy;
3:  1b732dbc ! 3:  d7eb6b89 lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
    @@ Commit message
     
      ## lib/utmp.c ##
     @@
    - #include <string.h>
      #include <fcntl.h>
      
    + #include "alloc/calloc.h"
     -#include "alloc/malloc.h"
    - #include "alloc/x/xcalloc.h"
      #include "attr.h"
      #include "sizeof.h"
    + #include "string/strchr/strnul.h"
     @@
      #include "string/strcmp/strprefix.h"
      #include "string/strcpy/strncpy.h"
      #include "string/strcpy/strtcpy.h"
     +#include "string/strdup/memdup.h"
    - #include "string/strdup/xstrdup.h"
    - #include "string/strdup/xstrndup.h"
    + #include "string/strdup/strdup.h"
    + #include "string/strdup/strndup.h"
      
     @@ lib/utmp.c: get_current_utmp(pid_t main_pid)
        if (NULL == ut)
v5
  • Add missing include.
  • Rename s/MEMDUP/memdup_a/.
$ git rd 
1:  565000388 ! 1:  c70395f34 lib/string/strdup/: memdup(), MEMDUP(): Add APIs
    @@ Metadata
     Author: Alejandro Colomar <alx@kernel.org>
     
      ## Commit message ##
    -    lib/string/strdup/: memdup(), MEMDUP(): Add APIs
    +    lib/string/strdup/: memdup[_a](): Add APIs
     
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
    @@ lib/string/strdup/memdup.h (new)
     +#include "attr.h"
     +
     +
    -+#define MEMDUP(p, T)  _Generic(p, T *: (T *) memdup(p, sizeof(T)))
    ++// memdup_a - memory duplicate array
    ++#define memdup_a(p, T)  _Generic(p, T *: (T *) memdup(p, sizeof(T)))
     +
     +
     +ATTR_MALLOC(free)
     +inline void *memdup(const void *p, size_t size);
     +
     +
    -+// memory duplicate
    ++// memdup - memory duplicate
     +inline void *
     +memdup(const void *p, size_t size)
     +{
2:  33cba4713 = 2:  e3d1bec95 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  d7eb6b89e ! 3:  5aff45d10 lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
    @@ Metadata
     Author: Alejandro Colomar <alx@kernel.org>
     
      ## Commit message ##
    -    lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
    +    lib/utmp.c: get_current_utmp(): Use memdup_a() instead of its pattern
     
         Cc: "Evgeny Grin (Karlson2k)" <k2k@drgrin.dev>
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
      ## lib/utmp.c ##
    -@@
    - #include <fcntl.h>
    - 
    - #include "alloc/calloc.h"
    --#include "alloc/malloc.h"
    - #include "attr.h"
    - #include "sizeof.h"
    - #include "string/strchr/strnul.h"
     @@
      #include "string/strcmp/strprefix.h"
      #include "string/strcpy/strncpy.h"
    @@ lib/utmp.c: get_current_utmp(pid_t main_pid)
     -          ut = ut_copy;
     -  }
     +  if (NULL != ut)
    -+          ut = MEMDUP(ut, struct utmpx);
    ++          ut = memdup_a(ut, struct utmpx);
      
        free(ut_by_line);
        free(ut_by_pid);
v6
  • Revert v5, which was totally bogus.
$ git rd 
1:  c70395f34 ! 1:  565000388 lib/string/strdup/: memdup[_a](): Add APIs
    @@ Metadata
     Author: Alejandro Colomar <alx@kernel.org>
     
      ## Commit message ##
    -    lib/string/strdup/: memdup[_a](): Add APIs
    +    lib/string/strdup/: memdup(), MEMDUP(): Add APIs
     
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
    @@ lib/string/strdup/memdup.h (new)
     +#include "attr.h"
     +
     +
    -+// memdup_a - memory duplicate array
    -+#define memdup_a(p, T)  _Generic(p, T *: (T *) memdup(p, sizeof(T)))
    ++#define MEMDUP(p, T)  _Generic(p, T *: (T *) memdup(p, sizeof(T)))
     +
     +
     +ATTR_MALLOC(free)
     +inline void *memdup(const void *p, size_t size);
     +
     +
    -+// memdup - memory duplicate
    ++// memory duplicate
     +inline void *
     +memdup(const void *p, size_t size)
     +{
2:  e3d1bec95 = 2:  33cba4713 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  5aff45d10 ! 3:  d7eb6b89e lib/utmp.c: get_current_utmp(): Use memdup_a() instead of its pattern
    @@ Metadata
     Author: Alejandro Colomar <alx@kernel.org>
     
      ## Commit message ##
    -    lib/utmp.c: get_current_utmp(): Use memdup_a() instead of its pattern
    +    lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
     
         Cc: "Evgeny Grin (Karlson2k)" <k2k@drgrin.dev>
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
      ## lib/utmp.c ##
    +@@
    + #include <fcntl.h>
    + 
    + #include "alloc/calloc.h"
    +-#include "alloc/malloc.h"
    + #include "attr.h"
    + #include "sizeof.h"
    + #include "string/strchr/strnul.h"
     @@
      #include "string/strcmp/strprefix.h"
      #include "string/strcpy/strncpy.h"
    @@ lib/utmp.c: get_current_utmp(pid_t main_pid)
     -          ut = ut_copy;
     -  }
     +  if (NULL != ut)
    -+          ut = memdup_a(ut, struct utmpx);
    ++          ut = MEMDUP(ut, struct utmpx);
      
        free(ut_by_line);
        free(ut_by_pid);
v6b
  • Rebase
$ git rd 
1:  565000388 ! 1:  3ab394e09 lib/string/strdup/: memdup(), MEMDUP(): Add APIs
    @@ lib/string/strdup/memdup.h (new)
     +inline void *memdup(const void *p, size_t size);
     +
     +
    -+// memory duplicate
    ++// memdup - memory duplicate
     +inline void *
     +memdup(const void *p, size_t size)
     +{
2:  33cba4713 = 2:  d85dc7819 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  d7eb6b89e ! 3:  a3f05ac24 lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
    @@ Commit message
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
      ## lib/utmp.c ##
    -@@
    - #include <fcntl.h>
    - 
    - #include "alloc/calloc.h"
    --#include "alloc/malloc.h"
    - #include "attr.h"
    - #include "sizeof.h"
    - #include "string/strchr/strnul.h"
     @@
      #include "string/strcmp/strprefix.h"
      #include "string/strcpy/strncpy.h"
v6c
  • Rebase
$ git rd 
1:  3ab394e09 = 1:  263f45103 lib/string/strdup/: memdup(), MEMDUP(): Add APIs
2:  d85dc7819 = 2:  fac3694f4 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  a3f05ac24 ! 3:  136086225 lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
    @@ Commit message
     
      ## lib/utmp.c ##
     @@
    + #include "string/strcmp/strneq.h"
      #include "string/strcmp/strprefix.h"
      #include "string/strcpy/strncpy.h"
    - #include "string/strcpy/strtcpy.h"
     +#include "string/strdup/memdup.h"
      #include "string/strdup/strdup.h"
      #include "string/strdup/strndup.h"
v6d
  • Rebase
$ git rd 
1:  263f45103 = 1:  3ef07b86e lib/string/strdup/: memdup(), MEMDUP(): Add APIs
2:  fac3694f4 = 2:  5dd726259 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  136086225 = 3:  cfa78b209 lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
v7
  • Change the implementation of MEMDUP() to use a compound literal instead of _Generic(3) to verify the type of the input pointer. It's as safe as the implementation with _Generic(3), and it's much more portable.
$ git rd 
1:  3ef07b86e ! 1:  a691448cf lib/string/strdup/: memdup(), MEMDUP(): Add APIs
    @@ lib/string/strdup/memdup.h (new)
     +#include "attr.h"
     +
     +
    -+#define MEMDUP(p, T)  _Generic(p, T *: (T *) memdup(p, sizeof(T)))
    ++#define MEMDUP(p, T)  ((T *) memdup((T *){(p)}, sizeof(T)))
     +
     +
     +ATTR_MALLOC(free)
2:  5dd726259 = 2:  9b29005dc lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  cfa78b209 = 3:  5899d2bc5 lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
v8
  • Use a compound literal to convert the return value, instead of a cast. Ideally, there should be zero casts in a program; they are very unsafe; let's not add more.
$ git rd 
1:  a691448cf ! 1:  f6fae4933 lib/string/strdup/: memdup(), MEMDUP(): Add APIs
    @@ lib/string/strdup/memdup.h (new)
     +#include "attr.h"
     +
     +
    -+#define MEMDUP(p, T)  ((T *) memdup((T *){(p)}, sizeof(T)))
    ++#define MEMDUP(p, T)  ((T *){memdup((T *){(p)}, sizeof(T))})
     +
     +
     +ATTR_MALLOC(free)
2:  9b29005dc = 2:  01fa1fe44 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  5899d2bc5 = 3:  05c3c42bc lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
v9
$ git range-diff shadow/master..gh/memdup T..memdup 
1:  f6fae4933 ! 1:  b4af6133f lib/string/strdup/: memdup(), MEMDUP(): Add APIs
    @@ Metadata
     Author: Alejandro Colomar <alx@kernel.org>
     
      ## Commit message ##
    -    lib/string/strdup/: memdup(), MEMDUP(): Add APIs
    +    lib/string/strdup/: memdup{,_T}(): Add APIs
     
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
    @@ lib/string/strdup/memdup.h (new)
     +#include <string.h>
     +
     +#include "attr.h"
    ++#include "sizeof.h"
     +
     +
    -+#define MEMDUP(p, T)  ((T *){memdup((T *){(p)}, sizeof(T))})
    ++// memdup_T - memory duplicate type-safe
    ++#define memdup_T(p, T)   memdup_T_(p, typeas(T))
    ++#define memdup_T_(p, T)  ((T *){memdup((T *){p}, sizeof(T))})
     +
     +
     +ATTR_MALLOC(free)
2:  01fa1fe44 ! 2:  84b2a9890 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
    @@ lib/utmp.c: get_current_utmp(pid_t main_pid)
        if (NULL != ut) {
                struct utmpx  *ut_copy;
      
    --          ut_copy = XMALLOC(1, struct utmpx);
    +-          ut_copy = xmalloc_T(1, struct utmpx);
     -          memcpy(ut_copy, ut, sizeof(*ut));
    -+          ut_copy = MALLOC(1, struct utmpx);
    ++          ut_copy = malloc_T(1, struct utmpx);
     +          if (ut_copy != NULL)
     +                  memcpy(ut_copy, ut, sizeof(*ut));
                ut = ut_copy;
3:  05c3c42bc ! 3:  5a9a01c90 lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
    @@ Metadata
     Author: Alejandro Colomar <alx@kernel.org>
     
      ## Commit message ##
    -    lib/utmp.c: get_current_utmp(): Use MEMDUP() instead of its pattern
    +    lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
     
         Cc: "Evgeny Grin (Karlson2k)" <k2k@drgrin.dev>
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
    @@ lib/utmp.c: get_current_utmp(pid_t main_pid)
     -  if (NULL != ut) {
     -          struct utmpx  *ut_copy;
     -
    --          ut_copy = MALLOC(1, struct utmpx);
    +-          ut_copy = malloc_T(1, struct utmpx);
     -          if (ut_copy != NULL)
     -                  memcpy(ut_copy, ut, sizeof(*ut));
     -          ut = ut_copy;
     -  }
     +  if (NULL != ut)
    -+          ut = MEMDUP(ut, struct utmpx);
    ++          ut = memdup_T(ut, struct utmpx);
      
        free(ut_by_line);
        free(ut_by_pid);
v9b
  • Rebase
$ git range-diff gh/T..gh/memdup T..memdup 
1:  b4af6133f = 1:  9d186b508 lib/string/strdup/: memdup{,_T}(): Add APIs
2:  84b2a9890 = 2:  57907b489 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  5a9a01c90 = 3:  c48eb5692 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v9c
  • Rebase
$ git range-diff gh/T..gh/memdup T..memdup 
1:  9d186b508 = 1:  dfa6f0c8a lib/string/strdup/: memdup{,_T}(): Add APIs
2:  57907b489 = 2:  4c082c4cf lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  c48eb5692 = 3:  77927e672 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v9d
  • Rebase
$ git range-diff gh/T..gh/memdup T..memdup 
1:  dfa6f0c8a = 1:  b6d26955c lib/string/strdup/: memdup{,_T}(): Add APIs
2:  4c082c4cf = 2:  7b9698018 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  77927e672 = 3:  c58a3f1db lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v9e
  • Rebase
$ git range-diff gh/T..gh/memdup T..memdup 
1:  b6d26955c = 1:  74aa3e690 lib/string/strdup/: memdup{,_T}(): Add APIs
2:  7b9698018 = 2:  0013ea4db lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  c58a3f1db = 3:  a39e81e41 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v9f
  • Rebase
$ git range-diff gh/T..gh/memdup T..memdup 
1:  74aa3e690 = 1:  e600b8c9c lib/string/strdup/: memdup{,_T}(): Add APIs
2:  0013ea4db = 2:  c51b8c172 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  a39e81e41 = 3:  385934812 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v9g
  • Rebase
$ git range-diff gh/T..gh/memdup T..memdup 
1:  e600b8c9c = 1:  23e4811ba lib/string/strdup/: memdup{,_T}(): Add APIs
2:  c51b8c172 = 2:  0ee01b22f lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  385934812 = 3:  8e9e314f6 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v10
  • Use register to disallow taking the address of compound literals.
$ git range-diff gh/T gh/memdup memdup 
1:  23e4811ba ! 1:  ccf3d8990 lib/string/strdup/: memdup{,_T}(): Add APIs
    @@ lib/string/strdup/memdup.h (new)
     +
     +// memdup_T - memory duplicate type-safe
     +#define memdup_T(p, T)   memdup_T_(p, typeas(T))
    -+#define memdup_T_(p, T)  ((T *){memdup((T *){p}, sizeof(T))})
    ++#define memdup_T_(p, T)  ((register T *){memdup((register T *){p}, sizeof(T))})
     +
     +
     +ATTR_MALLOC(free)
2:  0ee01b22f = 2:  cc9914013 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  8e9e314f6 = 3:  3360e0d67 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v10b
  • Rebase
$ git range-diff gh/T..gh/memdup T..memdup 
1:  ccf3d8990 = 1:  34cd0e788 lib/string/strdup/: memdup{,_T}(): Add APIs
2:  cc9914013 = 2:  0afcb1da4 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  3360e0d67 = 3:  1c61f303d lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v11
  • Use a statement expression to perform lvalue conversion on the compound literal. This avoids the issues of compound literals.
  • Use _Generic(3) to validate the argument type. This has slightly stronger type-safety guarantees than the compound literal, and also prevents the issues caused by the fact that compound literals are lvalues.
  • Allow const-qualified input.
$ git range-diff gh/T gh/memdup memdup 
1:  34cd0e788 ! 1:  feb6fde4d lib/string/strdup/: memdup{,_T}(): Add APIs
    @@ lib/string/strdup/memdup.h (new)
     +
     +// memdup_T - memory duplicate type-safe
     +#define memdup_T(p, T)   memdup_T_(p, typeas(T))
    -+#define memdup_T_(p, T)  ((register T *){memdup((register T *){p}, sizeof(T))})
    ++#define memdup_T_(p, T)                                               \
    ++({                                                                    \
    ++  _Generic(p, T *: 0, const T *: 0);                            \
    ++  (T *){memdup(p, sizeof(T))};                                  \
    ++})
     +
     +
     +ATTR_MALLOC(free)
2:  0afcb1da4 = 2:  d3b24dfed lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  1c61f303d = 3:  3cd5ca136 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v11b
  • Rebase
$ git range-diff gh/T..gh/memdup T..memdup 
1:  feb6fde4d = 1:  d2fb0444f lib/string/strdup/: memdup{,_T}(): Add APIs
2:  d3b24dfed = 2:  ee58b30d6 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  3cd5ca136 = 3:  94b4a8d61 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v11c
  • Rebase
$ git range-diff gh/T..gh/memdup T..memdup 
1:  e4f14282f = 1:  65ae3b33a lib/string/strdup/: memdup{,_T}(): Add APIs
2:  fa7692fd5 = 2:  11f0aae18 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  1cf10a6c6 = 3:  058cb96d4 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v11d
  • Rebase
$ git range-diff gh/T..gh/memdup T..memdup 
1:  65ae3b33a = 1:  252052244 lib/string/strdup/: memdup{,_T}(): Add APIs
2:  11f0aae18 = 2:  977a3d89d lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  058cb96d4 = 3:  9db3e652e lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v11e
  • Rebase
$ git range-diff gh/T..gh/memdup T..memdup 
1:  252052244 = 1:  36c658215 lib/string/strdup/: memdup{,_T}(): Add APIs
2:  977a3d89d = 2:  885d4030f lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  9db3e652e = 3:  f01f631cb lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v11f
  • Rebase
$ git range-diff gh/T..gh/memdup T..memdup 
1:  36c658215 = 1:  74b1a44b1 lib/string/strdup/: memdup{,_T}(): Add APIs
2:  885d4030f = 2:  9e445e2c4 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  f01f631cb = 3:  2bfd66b82 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v11g
  • Rebase
$ git range-diff gh/T..gh/memdup T..memdup 
1:  74b1a44b1 = 1:  dbbf1e799 lib/string/strdup/: memdup{,_T}(): Add APIs
2:  9e445e2c4 = 2:  5bce06bfd lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  2bfd66b82 = 3:  ea60e66f5 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v11h
  • Rebase
$ git range-diff gh/T..gh/memdup T..memdup 
1:  dbbf1e799 = 1:  1470b95ed lib/string/strdup/: memdup{,_T}(): Add APIs
2:  5bce06bfd = 2:  8a6fc556f lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  ea60e66f5 = 3:  4c5364d44 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v11i
  • Rebase
r$ git range-diff gh/T..gh/memdup T..memdup 
1:  1470b95ed = 1:  c30fc6091 lib/string/strdup/: memdup{,_T}(): Add APIs
2:  8a6fc556f = 2:  1beea513a lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  4c5364d44 = 3:  458b62ade lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v12
  • Rebase
$ git rd 
 1:  a190eb942 <  -:  --------- lib/alloc/: reallocarray[f]_(): Add helper macros to handle n?:1
 2:  52a19082b <  -:  --------- lib/: Use compound literals to avoid casts
 3:  a215b3b89 <  -:  --------- lib/alloc/: REALLOC[F](): Move _Generic(3) to separate line
 4:  e478a811f <  -:  --------- lib/, src/: Rename REALLOCF() => reallocf_T()
 5:  e756b580a <  -:  --------- lib/: Rename REALLOC() => realloc_T()
 6:  6d5a77730 <  -:  --------- lib/: Rename XREALLOC() => xrealloc_T()
 7:  966291ac4 <  -:  --------- lib/: Rename MALLOC() => malloc_T()
 8:  2ae0eda4e <  -:  --------- lib/: Rename XMALLOC() => xmalloc_T()
 9:  bf94bb229 <  -:  --------- lib/: Rename CALLOC() => calloc_T()
10:  22f581bf6 <  -:  --------- lib/: Rename XCALLOC() => xcalloc_T()
11:  d3eaf4ccc <  -:  --------- lib/search/: lsearch_T(): Don't return anything
12:  c30fc6091 =  1:  a86cf41e9 lib/string/strdup/: memdup{,_T}(): Add APIs
13:  1beea513a =  2:  35d00675e lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
14:  458b62ade =  3:  172dcfb9c lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v13
  • Use (void)0 within _Generic(3) to avoid -Wunused-value errors.
$ git rd 
1:  a86cf41e9 ! 1:  ef628d64c lib/string/strdup/: memdup{,_T}(): Add APIs
    @@ lib/string/strdup/memdup.h (new)
     +#define memdup_T(p, T)   memdup_T_(p, typeas(T))
     +#define memdup_T_(p, T)                                               \
     +({                                                                    \
    -+  _Generic(p, T *: 0, const T *: 0);                            \
    ++  _Generic(p, T *: (void)0, const T *: (void)0);                \
     +  (T *){memdup(p, sizeof(T))};                                  \
     +})
     +
2:  35d00675e = 2:  4fa6e4a77 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  172dcfb9c = 3:  b4f9f0e9c lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v13b
$ git rd 
1:  ef628d64c ! 1:  811324502 lib/string/strdup/: memdup{,_T}(): Add APIs
    @@ Metadata
      ## Commit message ##
         lib/string/strdup/: memdup{,_T}(): Add APIs
     
    +    Reviewed-by: "Evgeny Grin (Karlson2k)" <k2k@drgrin.dev>
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
      ## lib/Makefile.am ##
2:  4fa6e4a77 ! 2:  9da8d4712 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
    @@ Commit message
         This function already returned NULL on some errors.  It didn't make any
         sense to exit(3) on allocation failure.  Instead, just return NULL.
     
    +    Reviewed-by: "Evgeny Grin (Karlson2k)" <k2k@drgrin.dev>
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
      ## lib/utmp.c ##
3:  b4f9f0e9c ! 3:  b4bf0f400 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
    @@ Metadata
      ## Commit message ##
         lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
     
    -    Cc: "Evgeny Grin (Karlson2k)" <k2k@drgrin.dev>
    +    Reviewed-by: "Evgeny Grin (Karlson2k)" <k2k@drgrin.dev>
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
      ## lib/utmp.c ##
v13c
  • Rebase
$ git rd 
1:  811324502 = 1:  fb9c8da04 lib/string/strdup/: memdup{,_T}(): Add APIs
2:  9da8d4712 = 2:  46bd41428 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
3:  b4bf0f400 = 3:  741017f9d lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v14
  • Implement memdup_T() purely as a macro. This avoids raw calls to malloc(3) and memcpy(3), which are quite dangerous.
$ git rd 
2:  46bd41428 = 1:  fd6eb5763 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
-:  --------- > 2:  00307df2b lib/utmp.c: get_current_utmp(): Use simple assignment instead of memcpy(3)
1:  fb9c8da04 ! 3:  210277df3 lib/string/strdup/: memdup{,_T}(): Add APIs
    @@ Metadata
     Author: Alejandro Colomar <alx@kernel.org>
     
      ## Commit message ##
    -    lib/string/strdup/: memdup{,_T}(): Add APIs
    +    lib/string/strdup/: memdup_T(): Add API
     
    -    Reviewed-by: "Evgeny Grin (Karlson2k)" <k2k@drgrin.dev>
    +    Cc: "Evgeny Grin (Karlson2k)" <k2k@drgrin.dev>
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
      ## lib/Makefile.am ##
    @@ lib/string/strdup/memdup.c (new)
     +#include "config.h"
     +
     +#include "string/strdup/memdup.h"
    -+
    -+#include <stddef.h>
    -+
    -+
    -+extern inline void *memdup(const void *p, size_t size);
     
      ## lib/string/strdup/memdup.h (new) ##
     @@
    @@ lib/string/strdup/memdup.h (new)
     +#include "config.h"
     +
     +#include <stddef.h>
    -+#include <stdlib.h>
    -+#include <string.h>
     +
    -+#include "attr.h"
    -+#include "sizeof.h"
    ++#include "alloc/malloc.h"
     +
     +
     +// memdup_T - memory duplicate type-safe
     +#define memdup_T(p, T)   memdup_T_(p, typeas(T))
     +#define memdup_T_(p, T)                                               \
     +({                                                                    \
    -+  _Generic(p, T *: (void)0, const T *: (void)0);                \
    -+  (T *){memdup(p, sizeof(T))};                                  \
    ++  T  *new_;                                                     \
    ++                                                                      \
    ++  new_ = malloc_T(1, T);                                        \
    ++  if (new != NULL)                                              \
    ++          *new_ = *p;                                           \
    ++  new_;                                                         \
     +})
     +
     +
    -+ATTR_MALLOC(free)
    -+inline void *memdup(const void *p, size_t size);
    -+
    -+
    -+// memdup - memory duplicate
    -+inline void *
    -+memdup(const void *p, size_t size)
    -+{
    -+  void  *new;
    -+
    -+  new = malloc(size);
    -+  if (new == NULL)
    -+          return NULL;
    -+
    -+  return memcpy(new, p, size);
    -+}
    -+
    -+
     +#endif  // include guard
3:  741017f9d ! 4:  fbe2e3f6c lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
    @@ lib/utmp.c: get_current_utmp(pid_t main_pid)
     -
     -          ut_copy = malloc_T(1, struct utmpx);
     -          if (ut_copy != NULL)
    --                  memcpy(ut_copy, ut, sizeof(*ut));
    +-                  *ut_copy = *ut;
     -          ut = ut_copy;
     -  }
     +  if (NULL != ut)
v14b
  • Fix typo from v14.
$ git rd 
1:  fd6eb5763 = 1:  fd6eb5763 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
2:  00307df2b = 2:  00307df2b lib/utmp.c: get_current_utmp(): Use simple assignment instead of memcpy(3)
3:  210277df3 ! 3:  60cc58f16 lib/string/strdup/: memdup_T(): Add API
    @@ lib/string/strdup/memdup.h (new)
     +  T  *new_;                                                     \
     +                                                                      \
     +  new_ = malloc_T(1, T);                                        \
    -+  if (new != NULL)                                              \
    -+          *new_ = *p;                                           \
    ++  if (new_ != NULL)                                             \
    ++          *new_ = *(p);                                         \
     +  new_;                                                         \
     +})
     +
4:  fbe2e3f6c = 4:  98533af64 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v14c
  • Rebase
$ git rd 
1:  fd6eb5763 = 1:  30dbf1b10 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
2:  00307df2b = 2:  c04c945f5 lib/utmp.c: get_current_utmp(): Use simple assignment instead of memcpy(3)
3:  60cc58f16 = 3:  bab961680 lib/string/strdup/: memdup_T(): Add API
4:  98533af64 = 4:  0e37949b0 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern
v14d
  • Rebase
$ git rd 
1:  30dbf1b10 = 1:  733d66c66 lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
2:  c04c945f5 = 2:  1c88960a2 lib/utmp.c: get_current_utmp(): Use simple assignment instead of memcpy(3)
3:  bab961680 = 3:  c3eae6afb lib/string/strdup/: memdup_T(): Add API
4:  0e37949b0 = 4:  4cd63b4d2 lib/utmp.c: get_current_utmp(): Use memdup_T() instead of its pattern

@alejandro-colomar alejandro-colomar requested a review from hallyn July 16, 2025 15:47
@alejandro-colomar alejandro-colomar changed the title Add and use memdup(), MEMDUP() Add and use memdup(), MEMDUP(), instead of its pattern Jul 16, 2025
@alejandro-colomar alejandro-colomar marked this pull request as ready for review July 16, 2025 16:22
@Karlson2k
Copy link
Contributor

I still recommend adding attribute malloc (in form without destructor). When used without -fno-strict-aliasing (like this project) it improves optimisations.

@Karlson2k
Copy link
Contributor

See https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-malloc-function-attribute

Attribute malloc indicates that a function is malloc-like, i.e., that the pointer P returned by the function cannot alias any other pointer valid when the function returns, and moreover no pointers to valid objects occur in any storage addressed by P. In addition, GCC predicts that a function with the attribute returns non-null in most cases.

Independently, the form of the attribute with one or two arguments associates deallocator as a suitable deallocation function for pointers returned from the malloc-like function. ptr-index denotes the positional argument to which when the pointer is passed in calls to deallocator has the effect of deallocating it.

For example, besides stating that the functions return pointers that do not alias any others, the following declarations make fclose a suitable deallocator for pointers returned from all functions except popen, and pclose as the only suitable deallocator for pointers returned from popen. The deallocator functions must be declared before they can be referenced in the attribute.

int fclose (FILE*);
int pclose (FILE*);

__attribute__ ((malloc, malloc (fclose, 1)))
  FILE* fdopen (int, const char*);
__attribute__ ((malloc, malloc (fclose, 1)))
  FILE* fopen (const char*, const char*);
__attribute__ ((malloc, malloc (fclose, 1)))
  FILE* fmemopen(void *, size_t, const char *);
__attribute__ ((malloc, malloc (pclose, 1)))
  FILE* popen (const char*, const char*);
__attribute__ ((malloc, malloc (fclose, 1)))
  FILE* tmpfile (void);

@alejandro-colomar
Copy link
Collaborator Author

alejandro-colomar commented Jul 18, 2025

I still recommend adding attribute malloc (in form without destructor). When used without -fno-strict-aliasing (like this project) it improves optimisations.

I think by having this function declared inline the compiler should be able to realize about any possible optimizations, since it transparently sees the malloc(3) call.

I don't like using attributes for telling the compiler that it can optimize. That's error-prone. I prefer allowing it to see more of the code, so it can reason by itself.

@Karlson2k
Copy link
Contributor

I think by having this function declared inline the compiler should be able to realize about any possible optimizations, since it transparently sees the malloc(3) call.

Then you do not need current ATTR_MALLOC(free) as compiler may see malloc() call.
It looks inconsistent that function must have two attributes, but uses only one of them.

@alejandro-colomar
Copy link
Collaborator Author

alejandro-colomar commented Jul 18, 2025

I think by having this function declared inline the compiler should be able to realize about any possible optimizations, since it transparently sees the malloc(3) call.

Then you do not need current ATTR_MALLOC(free) as compiler may see malloc() call. It looks inconsistent that function must have two attributes, but uses only one of them.

The ATTR_MALLOC(free) attribute is not for optimization, but for safety. It allows the compiler to warn when we forget to free(3). There, redundancy increases safety.

@alejandro-colomar alejandro-colomar force-pushed the memdup branch 2 times, most recently from e6e4c80 to 903035d Compare July 18, 2025 20:12
@Karlson2k
Copy link
Contributor

Hard to review this PR as it has many not fully related changes.
The changes related to get_current_utmp() are fine.

@alejandro-colomar
Copy link
Collaborator Author

Hard to review this PR as it has many not fully related changes.

That's because this is queued after another PR.

Here's how it looks in my computer:

| * G 1cf10a6c6557 (gh/memdup) lib/utmp.c: get_current_utmp(): Use memdup_T() instead o>
| * G fa7692fd563c lib/utmp.c: get_current_utmp(): Don't exit(3) from library code
| * G e4f14282f695 lib/string/strdup/: memdup{,_T}(): Add APIs
| * G 2b2ad57fb4a4 (gh/T) lib/search/: lsearch_T(): Don't return anything
| * G 8de9d4e4a83e lib/: Rename XCALLOC() => xcalloc_T()
| * G 2d28653a905d lib/: Rename CALLOC() => calloc_T()
| * G 1fc1ebc87dd8 lib/: Rename XMALLOC() => xmalloc_T()
| * G f2a96d60b5ea lib/: Rename MALLOC() => malloc_T()
| * G d9e33b125ea9 lib/: Rename XREALLOC() => xrealloc_T()
| * G be0cd1060289 lib/: Rename REALLOC() => realloc_T()
| * G b11b780c0aad lib/, src/: Rename REALLOCF() => reallocf_T()
| * G 8622877a0981 lib/alloc/: REALLOC[F](): Move _Generic(3) to separate line
| * G 435059189086 lib/: Use compound literals to avoid casts
| * G 666f7a4e958f lib/alloc/: reallocarray[f]_(): Add helper macros to handle n?:1
| * G 8557266d399c lib/{alloc,search}/: Use typeas() to add support for arbitrary types
| * G c8b88d8f3aa0 lib/sizeof.h: typeas(): Add macro
| * G 0bb83a0bc1cf lib/search/: Split APIs
| * G 0ae8aac83929 lib/search/: Simplify CMP()
| * G 1725bb81ec19 lib/search/, lib/, src/: Add a type parameter to the type-safe macros
| * G b4044b01d2c7 lib/: Use a consistent name for macro arguments representing a type >
|/  

Once we merge the other branch, this will be small.

The changes related to get_current_utmp() are fine.

Thanks!

@alejandro-colomar alejandro-colomar force-pushed the memdup branch 6 times, most recently from ea60e66 to 4c5364d Compare December 5, 2025 15:36
@alejandro-colomar alejandro-colomar force-pushed the memdup branch 3 times, most recently from 172dcfb to b4f9f0e Compare December 6, 2025 12:09
@alejandro-colomar
Copy link
Collaborator Author

@Karlson2k , this is now ready. All dependencies have been merged.

@alejandro-colomar alejandro-colomar marked this pull request as ready for review December 6, 2025 12:12
@alejandro-colomar alejandro-colomar changed the title Add and use memdup(), MEMDUP(), instead of its pattern Add and use memdup[_T](), instead of its pattern Dec 6, 2025
@Karlson2k
Copy link
Contributor

Looks good!

@alejandro-colomar alejandro-colomar force-pushed the memdup branch 3 times, most recently from 741017f to fbe2e3f Compare December 10, 2025 14:24
@alejandro-colomar alejandro-colomar changed the title Add and use memdup[_T](), instead of its pattern Add and use memdup_T(), instead of its pattern Dec 10, 2025
@alejandro-colomar alejandro-colomar self-assigned this Dec 11, 2025
This function already returned NULL on some errors.  It didn't make any
sense to exit(3) on allocation failure.  Instead, just return NULL.

Reviewed-by: "Evgeny Grin (Karlson2k)" <k2k@drgrin.dev>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
…py(3)

memcpy(3) is overkill, and much more dangerous than simple assignment.
Simple assignment adds type safety, and removes any possibility of
buffer overflow due to accidentally specifying a wrong size.

Signed-off-by: Alejandro Colomar <alx@kernel.org>
Cc: "Evgeny Grin (Karlson2k)" <k2k@drgrin.dev>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
Reviewed-by: "Evgeny Grin (Karlson2k)" <k2k@drgrin.dev>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
@alejandro-colomar
Copy link
Collaborator Author

Cc: @kees

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

New API Simpler A good issue for a new beginner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants