Hi Arsen,

In <https://lists.gnu.org/archive/html/bug-gettext/2026-08/msg00000.html> you
write:
> Pure functions cannot have any observable effects other than their
> return value.  Thus, it is not valid for a pure function to have an
> out-parameter.
> ...
> Please, when you get the chance, audit the usages of _GL_ATTRIBUTE_PURE
> to check whether marked-pure functions have out-params.

Thank you for the suggestion. Just did an audit of Gnulib:
  - There are indeed 6 functions marked 'pure' but which have out parameters.
    They need to be marked 'reproducible', not 'pure'.
  - There are also a couple of functions marked 'pure' where it is not
    immediately clear from the prototype whether these have out parameters.

Fixed through these two patches:


2026-08-07  Bruno Haible  <[email protected]>

        Fix incorrect use of _GL_ATTRIBUTE_PURE.
        Reported by Arsen Arsenović <[email protected]> in
        <https://lists.gnu.org/archive/html/bug-gettext/2026-08/msg00000.html>.
        * lib/gl_array_omap.c (gl_array_search, gl_array_search_atleast): Use
        attribute _GL_ATTRIBUTE_REPRODUCIBLE instead of attribute
        _GL_ATTRIBUTE_PURE.
        * lib/gl_array_oset.c (gl_array_search_atleast): Likewise.
        * lib/gl_hash_map.c (gl_hash_search): Likewise.
        * lib/gl_linkedhash_map.c (gl_linkedhash_search): Likewise.
        * lib/modechange.h (mode_adjust): Likewise.

2026-08-07  Bruno Haible  <[email protected]>

        Clarify that "pure" functions don't have out parameters.
        * lib/acl-internal.h (acl_nontrivial, acl_ace_nontrivial,
        aclv_nontrivial): Make the 'entries' parameter a const pointer.
        * lib/acl-internal.c (acl_nontrivial, acl_ace_nontrivial,
        aclv_nontrivial): Likewise.
        * lib/dfa.c (state_separate_contexts): Make the 'd' parameter a const
        pointer.
        * lib/dirent.in.h (alphasort): Clarify prototype.
        * lib/fpending.h (__fpending): Likewise.
        * lib/freadable.h (freadable): Likewise.
        * lib/freadahead.h (freadahead): Likewise.
        * lib/freading.h (freading): Likewise.
        * lib/fwritable.h (fwritable): Likewise.
        * lib/fwriting.h (fwriting): Likewise.

>From 2d7434acbbb79519c266b778aba858f22e09697d Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sat, 8 Aug 2026 00:04:54 +0200
Subject: [PATCH 1/2] Clarify that "pure" functions don't have out parameters.

* lib/acl-internal.h (acl_nontrivial, acl_ace_nontrivial,
aclv_nontrivial): Make the 'entries' parameter a const pointer.
* lib/acl-internal.c (acl_nontrivial, acl_ace_nontrivial,
aclv_nontrivial): Likewise.
* lib/dfa.c (state_separate_contexts): Make the 'd' parameter a const
pointer.
* lib/dirent.in.h (alphasort): Clarify prototype.
* lib/fpending.h (__fpending): Likewise.
* lib/freadable.h (freadable): Likewise.
* lib/freadahead.h (freadahead): Likewise.
* lib/freading.h (freading): Likewise.
* lib/fwritable.h (fwritable): Likewise.
* lib/fwriting.h (fwriting): Likewise.
---
 ChangeLog          | 17 +++++++++++++++++
 lib/acl-internal.c | 22 +++++++++++-----------
 lib/acl-internal.h | 10 +++++-----
 lib/dfa.c          |  2 +-
 lib/dirent.in.h    |  2 +-
 lib/fpending.h     |  2 +-
 lib/freadable.h    |  2 +-
 lib/freadahead.h   |  2 +-
 lib/freading.h     |  2 +-
 lib/fwritable.h    |  2 +-
 lib/fwriting.h     |  2 +-
 11 files changed, 41 insertions(+), 24 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 521e657631..ec566864ff 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2026-08-07  Bruno Haible  <[email protected]>
+
+	Clarify that "pure" functions don't have out parameters.
+	* lib/acl-internal.h (acl_nontrivial, acl_ace_nontrivial,
+	aclv_nontrivial): Make the 'entries' parameter a const pointer.
+	* lib/acl-internal.c (acl_nontrivial, acl_ace_nontrivial,
+	aclv_nontrivial): Likewise.
+	* lib/dfa.c (state_separate_contexts): Make the 'd' parameter a const
+	pointer.
+	* lib/dirent.in.h (alphasort): Clarify prototype.
+	* lib/fpending.h (__fpending): Likewise.
+	* lib/freadable.h (freadable): Likewise.
+	* lib/freadahead.h (freadahead): Likewise.
+	* lib/freading.h (freading): Likewise.
+	* lib/fwritable.h (fwritable): Likewise.
+	* lib/fwriting.h (fwriting): Likewise.
+
 2026-08-04  Bruno Haible  <[email protected]>
 
 	readutmp: Add option to make use of wtmpdb.
diff --git a/lib/acl-internal.c b/lib/acl-internal.c
index 7603abed68..e012cc64bb 100644
--- a/lib/acl-internal.c
+++ b/lib/acl-internal.c
@@ -159,11 +159,11 @@ acl_default_nontrivial (acl_t acl)
    Return 1 if the given ACL, consisting of COUNT entries, is non-trivial.
    Return 0 if it is trivial, i.e. equivalent to a simple stat() mode.  */
 int
-acl_nontrivial (int count, aclent_t *entries)
+acl_nontrivial (int count, aclent_t const *entries)
 {
   for (int i = 0; i < count; i++)
     {
-      aclent_t *ace = &entries[i];
+      aclent_t const *ace = &entries[i];
 
       /* Note: If ace->a_type = USER_OBJ, ace->a_id is the st_uid from stat().
          If ace->a_type = GROUP_OBJ, ace->a_id is the st_gid from stat().
@@ -188,7 +188,7 @@ acl_nontrivial (int count, aclent_t *entries)
    Return 1 if the given ACL, consisting of COUNT entries, is non-trivial.
    Return 0 if it is trivial, i.e. equivalent to a simple stat() mode.  */
 int
-acl_ace_nontrivial (int count, ace_t *entries)
+acl_ace_nontrivial (int count, ace_t const *entries)
 {
   /* The flags in the ace_t structure changed in a binary incompatible way
      when ACL_NO_TRIVIAL etc. were introduced in <sys/acl.h> version 1.15.
@@ -209,7 +209,7 @@ acl_ace_nontrivial (int count, ace_t *entries)
     /* Running on Solaris 10.  */
     for (int i = 0; i < count; i++)
       {
-        ace_t *ace = &entries[i];
+        ace_t const *ace = &entries[i];
 
         /* Note:
            If ace->a_flags = ACE_OWNER, ace->a_who is the st_uid from stat().
@@ -236,7 +236,7 @@ acl_ace_nontrivial (int count, ace_t *entries)
 
       for (int i = 0; i < count; i++)
         {
-          ace_t *ace = &entries[i];
+          ace_t const *ace = &entries[i];
 
           unsigned int index1;
           if (ace->a_type == NEW_ACE_ACCESS_ALLOWED_ACE_TYPE)
@@ -368,14 +368,14 @@ acl_ace_nontrivial (int count, ace_t *entries)
 /* Return 1 if the given ACL is non-trivial.
    Return 0 if it is trivial, i.e. equivalent to a simple stat() mode.  */
 int
-acl_nontrivial (int count, struct acl_entry *entries)
+acl_nontrivial (int count, struct acl_entry const *entries)
 {
   if (count > 3)
     return 1;
 
   for (int i = 0; i < count; i++)
     {
-      struct acl_entry *ace = &entries[i];
+      struct acl_entry const *ace = &entries[i];
 
       if (ace->uid != ACL_NSUSER && ace->gid != ACL_NSGROUP)
         return 1;
@@ -388,11 +388,11 @@ acl_nontrivial (int count, struct acl_entry *entries)
 /* Return 1 if the given ACL is non-trivial.
    Return 0 if it is trivial, i.e. equivalent to a simple stat() mode.  */
 int
-aclv_nontrivial (int count, struct acl *entries)
+aclv_nontrivial (int count, struct acl const *entries)
 {
   for (int i = 0; i < count; i++)
     {
-      struct acl *ace = &entries[i];
+      struct acl const *ace = &entries[i];
 
       /* Note: If ace->a_type = USER_OBJ, ace->a_id is the st_uid from stat().
          If ace->a_type = GROUP_OBJ, ace->a_id is the st_gid from stat().
@@ -471,11 +471,11 @@ acl_nfs4_nontrivial (nfs4_acl_int_t *a)
    Return 1 if the given ACL, consisting of COUNT entries, is non-trivial.
    Return 0 if it is trivial, i.e. equivalent to a simple stat() mode.  */
 int
-acl_nontrivial (int count, struct acl *entries)
+acl_nontrivial (int count, struct acl const *entries)
 {
   for (int i = 0; i < count; i++)
     {
-      struct acl *ace = &entries[i];
+      struct acl const *ace = &entries[i];
 
       /* Note: If ace->a_type = USER_OBJ, ace->a_id is the st_uid from stat().
          If ace->a_type = GROUP_OBJ, ace->a_id is the st_gid from stat().
diff --git a/lib/acl-internal.h b/lib/acl-internal.h
index eafb4d027f..36be1f657d 100644
--- a/lib/acl-internal.h
+++ b/lib/acl-internal.h
@@ -156,14 +156,14 @@ extern int acl_default_nontrivial (acl_t);
 
 /* Return 1 if the given ACL is non-trivial.
    Return 0 if it is trivial, i.e. equivalent to a simple stat() mode.  */
-extern int acl_nontrivial (int count, aclent_t *entries) _GL_ATTRIBUTE_PURE;
+extern int acl_nontrivial (int count, aclent_t const *entries) _GL_ATTRIBUTE_PURE;
 
 #  ifdef ACE_GETACL /* Solaris 10 */
 
 /* Test an ACL retrieved with ACE_GETACL.
    Return 1 if the given ACL, consisting of COUNT entries, is non-trivial.
    Return 0 if it is trivial, i.e. equivalent to a simple stat() mode.  */
-extern int acl_ace_nontrivial (int count, ace_t *entries) _GL_ATTRIBUTE_PURE;
+extern int acl_ace_nontrivial (int count, ace_t const *entries) _GL_ATTRIBUTE_PURE;
 
 /* Definitions for when the built executable is executed on Solaris 10
    (newer version) or Solaris 11.  */
@@ -202,13 +202,13 @@ extern int acl_ace_nontrivial (int count, ace_t *entries) _GL_ATTRIBUTE_PURE;
 
 /* Return 1 if the given ACL is non-trivial.
    Return 0 if it is trivial, i.e. equivalent to a simple stat() mode.  */
-extern int acl_nontrivial (int count, struct acl_entry *entries);
+extern int acl_nontrivial (int count, struct acl_entry const *entries);
 
 #  if HAVE_ACLV_H /* HP-UX >= 11.11 */
 
 /* Return 1 if the given ACL is non-trivial.
    Return 0 if it is trivial, i.e. equivalent to a simple stat() mode.  */
-extern int aclv_nontrivial (int count, struct acl *entries);
+extern int aclv_nontrivial (int count, struct acl const *entries);
 
 #  endif
 
@@ -226,7 +226,7 @@ extern int acl_nontrivial (struct acl *a);
 
 /* Return 1 if the given ACL is non-trivial.
    Return 0 if it is trivial, i.e. equivalent to a simple stat() mode.  */
-extern int acl_nontrivial (int count, struct acl *entries);
+extern int acl_nontrivial (int count, struct acl const *entries);
 
 # endif
 
diff --git a/lib/dfa.c b/lib/dfa.c
index 13dae682ed..b84d86feef 100644
--- a/lib/dfa.c
+++ b/lib/dfa.c
@@ -2402,7 +2402,7 @@ charclass_context (struct dfa const *dfa, charclass const *c)
    in the complement set will have the same follow set.  */
 
 static int _GL_ATTRIBUTE_PURE
-state_separate_contexts (struct dfa *d, position_set const *s)
+state_separate_contexts (struct dfa const *d, position_set const *s)
 {
   int separate_contexts = 0;
 
diff --git a/lib/dirent.in.h b/lib/dirent.in.h
index 9198c1d308..0a32637b8a 100644
--- a/lib/dirent.in.h
+++ b/lib/dirent.in.h
@@ -367,7 +367,7 @@ _GL_WARN_ON_USE (scandir, "scandir is unportable - "
 /* Compare two 'struct dirent' entries alphabetically.  */
 # if !@HAVE_ALPHASORT@
 _GL_FUNCDECL_SYS (alphasort, int,
-                  (const struct dirent **, const struct dirent **),
+                  (const struct dirent * /*const*/ *, const struct dirent * /*const*/ *),
                   _GL_ATTRIBUTE_PURE
                   _GL_ARG_NONNULL ((1, 2)));
 # endif
diff --git a/lib/fpending.h b/lib/fpending.h
index aeb1d9310d..dd4e74b750 100644
--- a/lib/fpending.h
+++ b/lib/fpending.h
@@ -36,7 +36,7 @@ extern "C" {
 
 
 #if !HAVE_DECL___FPENDING
-size_t __fpending (FILE *) _GL_ATTRIBUTE_PURE;
+size_t __fpending (FILE /*const*/ *) _GL_ATTRIBUTE_PURE;
 #endif
 
 
diff --git a/lib/freadable.h b/lib/freadable.h
index ceaa7ef34f..d0c1faf593 100644
--- a/lib/freadable.h
+++ b/lib/freadable.h
@@ -40,7 +40,7 @@
 extern "C" {
 # endif
 
-extern bool freadable (FILE *stream) _GL_ATTRIBUTE_PURE;
+extern bool freadable (FILE /*const*/ *stream) _GL_ATTRIBUTE_PURE;
 
 # ifdef __cplusplus
 }
diff --git a/lib/freadahead.h b/lib/freadahead.h
index 4f441ae454..ad5c476dbe 100644
--- a/lib/freadahead.h
+++ b/lib/freadahead.h
@@ -43,7 +43,7 @@
 extern "C" {
 # endif
 
-extern size_t freadahead (FILE *stream) _GL_ATTRIBUTE_PURE;
+extern size_t freadahead (FILE /*const*/ *stream) _GL_ATTRIBUTE_PURE;
 
 # ifdef __cplusplus
 }
diff --git a/lib/freading.h b/lib/freading.h
index 3d75d75e3a..62e6cbda1b 100644
--- a/lib/freading.h
+++ b/lib/freading.h
@@ -50,7 +50,7 @@
 extern "C" {
 # endif
 
-extern bool freading (FILE *stream) _GL_ATTRIBUTE_PURE;
+extern bool freading (FILE /*const*/ *stream) _GL_ATTRIBUTE_PURE;
 
 # ifdef __cplusplus
 }
diff --git a/lib/fwritable.h b/lib/fwritable.h
index 23888e50fb..ba9b4cd9ee 100644
--- a/lib/fwritable.h
+++ b/lib/fwritable.h
@@ -40,7 +40,7 @@
 extern "C" {
 # endif
 
-extern bool fwritable (FILE *stream) _GL_ATTRIBUTE_PURE;
+extern bool fwritable (FILE /*const*/ *stream) _GL_ATTRIBUTE_PURE;
 
 # ifdef __cplusplus
 }
diff --git a/lib/fwriting.h b/lib/fwriting.h
index b42d999df8..a88e50a73b 100644
--- a/lib/fwriting.h
+++ b/lib/fwriting.h
@@ -51,7 +51,7 @@
 extern "C" {
 # endif
 
-extern bool fwriting (FILE *stream) _GL_ATTRIBUTE_PURE;
+extern bool fwriting (FILE /*const*/ *stream) _GL_ATTRIBUTE_PURE;
 
 # ifdef __cplusplus
 }
-- 
2.53.0

From f9b4c8013b1880e7b3953f3ba6f233ba5b0cd8c5 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sat, 8 Aug 2026 00:09:48 +0200
Subject: [PATCH 2/2] Fix incorrect use of _GL_ATTRIBUTE_PURE.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Reported by Arsen Arsenović <[email protected]> in
<https://lists.gnu.org/archive/html/bug-gettext/2026-08/msg00000.html>.

* lib/gl_array_omap.c (gl_array_search, gl_array_search_atleast): Use
attribute _GL_ATTRIBUTE_REPRODUCIBLE instead of attribute
_GL_ATTRIBUTE_PURE.
* lib/gl_array_oset.c (gl_array_search_atleast): Likewise.
* lib/gl_hash_map.c (gl_hash_search): Likewise.
* lib/gl_linkedhash_map.c (gl_linkedhash_search): Likewise.
* lib/modechange.h (mode_adjust): Likewise.
---
 ChangeLog               | 13 +++++++++++++
 lib/gl_array_omap.c     |  4 ++--
 lib/gl_array_oset.c     |  2 +-
 lib/gl_hash_map.c       |  2 +-
 lib/gl_linkedhash_map.c |  2 +-
 lib/modechange.h        |  2 +-
 6 files changed, 19 insertions(+), 6 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index ec566864ff..1e26e6875e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2026-08-07  Bruno Haible  <[email protected]>
+
+	Fix incorrect use of _GL_ATTRIBUTE_PURE.
+	Reported by Arsen Arsenović <[email protected]> in
+	<https://lists.gnu.org/archive/html/bug-gettext/2026-08/msg00000.html>.
+	* lib/gl_array_omap.c (gl_array_search, gl_array_search_atleast): Use
+	attribute _GL_ATTRIBUTE_REPRODUCIBLE instead of attribute
+	_GL_ATTRIBUTE_PURE.
+	* lib/gl_array_oset.c (gl_array_search_atleast): Likewise.
+	* lib/gl_hash_map.c (gl_hash_search): Likewise.
+	* lib/gl_linkedhash_map.c (gl_linkedhash_search): Likewise.
+	* lib/modechange.h (mode_adjust): Likewise.
+
 2026-08-07  Bruno Haible  <[email protected]>
 
 	Clarify that "pure" functions don't have out parameters.
diff --git a/lib/gl_array_omap.c b/lib/gl_array_omap.c
index be1b9151ad..0fda12ce7a 100644
--- a/lib/gl_array_omap.c
+++ b/lib/gl_array_omap.c
@@ -110,7 +110,7 @@ gl_array_indexof (gl_omap_t map, const void *key)
   return (size_t)(-1);
 }
 
-static bool _GL_ATTRIBUTE_PURE
+static bool _GL_ATTRIBUTE_REPRODUCIBLE
 gl_array_search (gl_omap_t map, const void *key, const void **valuep)
 {
   size_t index = gl_array_indexof (map, key);
@@ -123,7 +123,7 @@ gl_array_search (gl_omap_t map, const void *key, const void **valuep)
     return false;
 }
 
-static bool _GL_ATTRIBUTE_PURE
+static bool _GL_ATTRIBUTE_REPRODUCIBLE
 gl_array_search_atleast (gl_omap_t map,
                          gl_mapkey_threshold_fn threshold_fn,
                          const void *threshold,
diff --git a/lib/gl_array_oset.c b/lib/gl_array_oset.c
index 5672e1e4c0..03afec519e 100644
--- a/lib/gl_array_oset.c
+++ b/lib/gl_array_oset.c
@@ -161,7 +161,7 @@ gl_array_indexof_atleast (gl_oset_t set,
   return count;
 }
 
-static bool _GL_ATTRIBUTE_PURE
+static bool _GL_ATTRIBUTE_REPRODUCIBLE
 gl_array_search_atleast (gl_oset_t set,
                          gl_setelement_threshold_fn threshold_fn,
                          const void *threshold,
diff --git a/lib/gl_hash_map.c b/lib/gl_hash_map.c
index 1598515641..3767b17cae 100644
--- a/lib/gl_hash_map.c
+++ b/lib/gl_hash_map.c
@@ -95,7 +95,7 @@ gl_hash_size (gl_map_t map)
   return map->count;
 }
 
-static bool _GL_ATTRIBUTE_PURE
+static bool _GL_ATTRIBUTE_REPRODUCIBLE
 gl_hash_search (gl_map_t map, const void *key, const void **valuep)
 {
   size_t hashcode =
diff --git a/lib/gl_linkedhash_map.c b/lib/gl_linkedhash_map.c
index fd9e786128..3b85099a60 100644
--- a/lib/gl_linkedhash_map.c
+++ b/lib/gl_linkedhash_map.c
@@ -120,7 +120,7 @@ gl_linkedhash_size (gl_map_t map)
   return map->count;
 }
 
-static bool _GL_ATTRIBUTE_PURE
+static bool _GL_ATTRIBUTE_REPRODUCIBLE
 gl_linkedhash_search (gl_map_t map, const void *key, const void **valuep)
 {
   size_t hashcode =
diff --git a/lib/modechange.h b/lib/modechange.h
index eed2ffb18f..2b5a2c29a1 100644
--- a/lib/modechange.h
+++ b/lib/modechange.h
@@ -38,7 +38,7 @@ struct mode_change *mode_create_from_ref (const char *)
   _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE;
 mode_t mode_adjust (mode_t, bool, mode_t, struct mode_change const *,
                     mode_t *)
-  _GL_ATTRIBUTE_PURE;
+  _GL_ATTRIBUTE_REPRODUCIBLE;
 
 
 #ifdef __cplusplus
-- 
2.53.0

Reply via email to