Re: incorrect use of 'pure' attribute

Bruno Haible via Gnulib discussion list <[email protected]>
Newsgroups gmane.comp.lib.gnulib.bugs
Message-ID <3146070.o7ts2hSHzF@cagnes>
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.
0001-Clarify-that-pure-functions-don-t-have-out-parameter.patch (text/x-patch, 10.4 KB)
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
0002-Fix-incorrect-use-of-_GL_ATTRIBUTE_PURE.patch (text/x-patch, 4.2 KB)
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.