Following up on my December 7 email, it seems to me that just as GNU C __attribute__((const)) does not imply C23 [[unsequenced]], GNU C __attribute__((pure)) does not imply C23 [[reproducible]]. Also, Gnulib should be fixed to not assume either implication. I installed the attached Gnulib patch to do that, and to add commentary that I think reflects both C23 and GNU C. This area continues to be obscure, though, and if I made mistakes in commentary or code please let me know.

I think that luckily the fix makes little difference in practice today, as every current compiler that supports C23 [[unsequenced]] and [[reproducible]] also supports GNU C __attribute__((const)) and __attribute__((pure)). However, the fix might fix bugs in applications compiled by future C compilers that support the C23 but not the GNU C attributes. Although the fix will also surely slow down those applications, that's better than possibly having the bugs.

I ran across this issue when looking into some non-GNU code (the Time Zone Database), which has a private header that incorrectly assumes that __attribute__((const)) is stricter than [[unsequenced]] and that __attribute__((pure)) is stricter than [[reproducible]]. I plan to fix that too.
From 8864e1c0f994dc80eac8a9b1a364e20cad93d67e Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email protected]>
Date: Sat, 20 Dec 2025 10:37:30 -0800
Subject: [PATCH] Fix [[unsequenced]] and [[reproducible]] confusion

* m4/gnulib-common.m4 (_GL_ATTRIBUTE_CONST, _GL_ATTRIBUTE_PURE):
Default to empty, not to _GL_ATTRIBUTE_UNSEQUENCED and
_GL_ATTRIBUTE_REPRODUCIBLE respectively, because unfortunately
__attribute__ ((const)) does not imply [[unsequenced]] and
__attribute__ ((pure)) does not imply [[reproducible]].
---
 ChangeLog           |  7 +++++++
 m4/gnulib-common.m4 | 48 +++++++++++++++++++++++++--------------------
 2 files changed, 34 insertions(+), 21 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 891c2edb67..161f0ce585 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
 2025-12-20  Paul Eggert  <[email protected]>
 
+	Fix [[unsequenced]] and [[reproducible]] confusion
+	* m4/gnulib-common.m4 (_GL_ATTRIBUTE_CONST, _GL_ATTRIBUTE_PURE):
+	Default to empty, not to _GL_ATTRIBUTE_UNSEQUENCED and
+	_GL_ATTRIBUTE_REPRODUCIBLE respectively, because unfortunately
+	__attribute__ ((const)) does not imply [[unsequenced]] and
+	__attribute__ ((pure)) does not imply [[reproducible]].
+
 	timespec: tweak timespec_sign
 	* lib/timespec.h (timespec_cmp, timespec_sign):
 	These functions are const, not merely pure.
diff --git a/m4/gnulib-common.m4 b/m4/gnulib-common.m4
index 7ec965c0e4..9cdb21ac26 100644
--- a/m4/gnulib-common.m4
+++ b/m4/gnulib-common.m4
@@ -422,15 +422,17 @@ AC_DEFUN([gl_COMMON_BODY], [
    without examining state, and always returns exactly once -
    e.g., does not raise an exception, call longjmp, or loop forever.
    (This attribute is stricter than _GL_ATTRIBUTE_PURE because the
-   function cannot observe state.  It is stricter than
-   _GL_ATTRIBUTE_UNSEQUENCED because the function must return exactly
-   once and cannot access state addressed by its arguments.)  */
+   function cannot observe state.  Unlike _GL_ATTRIBUTE_UNSEQUENCED
+   the function must return exactly once and cannot access state
+   addressed by its pointer arguments or that happens to have the same
+   value for all calls to the function, but the function is allowed to
+   return a pointer to storage that can be modified later.  */
 /* Applies to: functions.  */
 #ifndef _GL_ATTRIBUTE_CONST
 # if _GL_HAS_ATTRIBUTE (const)
 #  define _GL_ATTRIBUTE_CONST __attribute__ ((__const__))
 # else
-#  define _GL_ATTRIBUTE_CONST _GL_ATTRIBUTE_UNSEQUENCED
+#  define _GL_ATTRIBUTE_CONST
 # endif
 #endif
 
@@ -752,30 +754,31 @@ AC_DEFUN([gl_COMMON_BODY], [
    returns a value determined solely by its arguments's values
    together with observable state, and always returns exactly once.
    (This attribute is looser than _GL_ATTRIBUTE_CONST because the function
-   can depend on observable state.  It is stricter than
-   _GL_ATTRIBUTE_REPRODUCIBLE because the function must return exactly
-   once and cannot change state addressed by its arguments.)  */
+   can depend on observable state.
+   Unlike _GL_ATTRIBUTE_REPRODUCIBLE the function must return exactly
+   once and cannot change state addressed by its arguments, but the
+   function can return a pointer to storage whose contents change later.)  */
 /* Applies to: functions.  */
 #ifndef _GL_ATTRIBUTE_PURE
 # if _GL_HAS_ATTRIBUTE (pure)
 #  define _GL_ATTRIBUTE_PURE __attribute__ ((__pure__))
 # else
-#  define _GL_ATTRIBUTE_PURE _GL_ATTRIBUTE_REPRODUCIBLE
+#  define _GL_ATTRIBUTE_PURE
 # endif
 #endif
 
 /* _GL_ATTRIBUTE_REPRODUCIBLE declares:
    It is OK for a compiler to move a call, or omit a duplicate call
-   and reuse a cached value returned either directly or indirectly
-   via a pointer argument, if other observable state is the same;
-   however, these pointer arguments cannot alias.
+   and reuse a cached value returned either directly or indirectly via
+   a pointer, if other observable state is the same;
+   however, pointer arguments cannot alias.
    This attribute is safe for a function that is effectless and idempotent;
    see ISO C 23 § 6.7.13.8 for a definition of these terms.
    (This attribute is looser than _GL_ATTRIBUTE_UNSEQUENCED because
-   the function need not be stateless or independent.  It is looser
-   from _GL_ATTRIBUTE_PURE because the function need not return
-   exactly once, and it can change state addressed by its pointer arguments
-   that do not alias.)
+   the function need not be stateless or independent.
+   Unlike _GL_ATTRIBUTE_PURE the function need not return exactly once
+   and can change state addressed by its pointer arguments, but the
+   function cannot return a pointer to storage whose contents change later.)
    See also <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2956.htm> and
    <https://stackoverflow.com/questions/76847905/>.
    ATTENTION! Efforts are underway to change the meaning of this attribute.
@@ -820,16 +823,19 @@ AC_DEFUN([gl_COMMON_BODY], [
 
 /* _GL_ATTRIBUTE_UNSEQUENCED declares:
    It is OK for a compiler to move a call, or omit a duplicate call
-   and reuse a cached return value, addressed by its arguments is the same.
+   and reuse a cached value returned either directly or indirectly via
+   a pointer, if the state addressed by its pointer arguments is the same;
+   however, pointer arguments cannot alias.
    This attribute is safe for a function that is effectless, idempotent,
    stateless, and independent; see ISO C 23 § 6.7.13.8 for a definition of
    these terms.
    (This attribute is stricter than _GL_ATTRIBUTE_REPRODUCIBLE because
-   the function must be stateless and independent.  It differs from
-   _GL_ATTRIBUTE_CONST because the function need not return exactly
-   once and can depend on state accessed via its pointer arguments
-   that do not alias, or on other state that happens to have the
-   same value for all calls to the function.)
+   the function must be stateless and independent.  Unlike
+   _GL_ATTRIBUTE_CONST the function need not return exactly once, and
+   can depend on state accessed via its pointer arguments or that
+   happens to have the same value for all calls to the function, but
+   the function cannot return a pointer to storage whose contents
+   change later.)
    See also <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2956.htm> and
    <https://stackoverflow.com/questions/76847905/>.
    ATTENTION! Efforts are underway to change the meaning of this attribute.
-- 
2.51.0

Reply via email to