https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126952
Roger Sayle <roger at nextmovesoftware dot com> changed:
What |Removed |Added
----------------------------------------------------------------------------
Last reconfirmed| |2026-10-08
Ever confirmed|0 |1
Status|UNCONFIRMED |NEW
CC| |roger at nextmovesoftware dot
com
--- Comment #7 from Roger Sayle <roger at nextmovesoftware dot com> ---
Firstly, thanks to Andrea for pointing out that my patch posted at:
https://gcc.gnu.org/pipermail/gcc-patches/2026-October/733683.html
was associated with PR middle-end/126952.
It's interesting that the bisection points to jump threading and that this is
believed to be a false positive warning, as whilst developing/reducing the
patch above (to the minimal change that would restore bootstrap), that I
initially diagnosed the tree cause being elsewhere.
I believe that a minimal reproducer of the true cause is:
wide_int x;
wide_int y;
x = y;
Obviously, the above uses an uninitialized value (as would be the case if the
type was int), but for the current implementation of wide_int it also invokes
undefined behavior, possibly overwriting memory, corrupting the stack and/or
segfaulting.
This is because wide_int effectively contains three fields; val, len and
precision, and the default constructor only initializes one of them:
inline wide_int_storage::wide_int_storage () : precision (0) {}
Remembering this is you then study the implementation of wi::copy:
template <typename T1, typename T2>
inline void
wi::copy (T1 &x, const T2 &y)
{
unsigned int len = y.get_len ();
HOST_WIDE_INT *xval = x.write_val (len);
const HOST_WIDE_INT *yval = y.get_val ();
unsigned int i = 0;
do
xval[i] = yval[i];
while (++i < len);
/* For widest_int write_val is called with an exact value, not
upper bound for len, so nothing is needed further. */
if (!wi::int_traits <T1>::needs_write_val_arg)
x.set_len (len, y.is_sign_extended);
}
You'll notice that it never checks "precision" and instead overwrites "len"
words at wherever write_val decides to point to. Boom goes the dynamite.
So my first attempt at a patch looked like:
diff --git a/gcc/wide-int.h b/gcc/wide-int.h
index 9c30f320b91..2ff028e571b 100644
--- a/gcc/wide-int.h
+++ b/gcc/wide-int.h
@@ -706,7 +706,7 @@ namespace wi
class storage_ref
{
public:
- storage_ref () {}
+ storage_ref () : val (0), len (0), precision (0) {}
storage_ref (const HOST_WIDE_INT *, unsigned int, unsigned int);
const HOST_WIDE_INT *val;
@@ -1174,7 +1174,7 @@ namespace wi
};
}
-inline wide_int_storage::wide_int_storage () : precision (0) {}
+inline wide_int_storage::wide_int_storage () : len (0), precision (0) {}
/* Initialize the storage from integer X, in its natural precision.
Note that we do not allow integers with host-dependent precision
@@ -1356,7 +1356,7 @@ private:
unsigned int len;
public:
- fixed_wide_int_storage () = default;
+ fixed_wide_int_storage () : len (0) {}
template <typename T>
fixed_wide_int_storage (const T &);
Alas I was worried that this potentially had a compile-time impact, with
an additional write to memory for each wide_int construction that didn't
specify an initial value. Hence, the proposed fix was to avoid having
(and copying) any uninitialized wide_int in this PR's initial description.
Of course, correctly diagnosing that copying an uninitialized wide_int should
generate a "may be used uninitialized" error, is heavily dependent of inlining,
loop unrolling, jump threading etc. So Richard's bisected patch is actually
doing a better job, and my posted patch is indeed hiding/avoiding the true
cause.
I'd be curious if folks agree with my analysis. I wouldn't be surprised if it
turns out that its the "while (i++ < len)" backedge in wi::copy that is now
threaded differently and allows the "used uninitialized" to correctly identify
the problem (which may have been silenced by the earlier UB writes to memory).