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