https://gcc.gnu.org/g:aa9d3f17ff359ecad729a9436adbbf644aab2426

commit r15-9201-gaa9d3f17ff359ecad729a9436adbbf644aab2426
Author: Jakub Jelinek <ja...@redhat.com>
Date:   Fri Apr 4 17:27:56 2025 +0200

    c++, libcpp: Allow some left shifts in the preprocessor [PR119391]
    
    The libcpp left shift handling implements (partially) the C99-C23
    wording where shifts are UB if shift count is negative, or too large,
    or shifting left a negative value or shifting left non-negative value
    results in something not representable in the result type (in the
    preprocessor case that is intmax_t).
    libcpp actually implements left shift by negative count as right shifts
    by negation of the count and similarly right shifts by negative count
    as left shifts by negation (not ok), sets overflow for too large shift
    count (ok), doesn't check for negative values on left shift (not ok)
    and checks correctly for the non-representable ones otherwise (ok).
    
    Now, C++11 to C++17 has different behavior, whereas in C99-C23 1 << 63
    in preprocessor is invalid, in C++11-17 it is valid, but 3 << 63 is
    not.  The wording is that left shift of negative value is UB (like in C)
    and signed non-negative left shift is UB if the result isn't representable
    in corresponding unsigned type (so uintmax_t for libcpp).
    
    And then C++20 and newer says all left shifts are well defined with the
    exception of bad shift counts.
    
    In -fsanitize=undefined we handle these by
      /* For signed x << y, in C99 and later, the following:
         (unsigned) x >> (uprecm1 - y)
         if non-zero, is undefined.  */
    and
      /* For signed x << y, in C++11 to C++17, the following:
         x < 0 || ((unsigned) x >> (uprecm1 - y))
         if > 1, is undefined.  */
    
    Now, we are late in GCC 15 development, so I think making the preprocessor
    more strict than it is now is undesirable, so will defer setting overflow
    flag for the shifts by negative count, or shifts by negative value left.
    
    The following patch just makes some previously incorrectly rejected or
    warned cases valid for C++11-17 and even more for C++20 and later.
    
    2025-04-04  Jakub Jelinek  <ja...@redhat.com>
    
            PR preprocessor/119391
            * expr.cc (num_lshift): Add pfile argument.  Don't set num.overflow
            for !num.unsignedp in C++20 or later unless n >= precision.  For
            C++11 to C++17 set it if orig >> (precision - 1 - n) as logical
            shift results in value > 1.
            (num_binary_op): Pass pfile to num_lshift.
            (num_div_op): Likewise.
    
            * g++.dg/cpp/pr119391.C: New test.

Diff:
---
 gcc/testsuite/g++.dg/cpp/pr119391.C | 15 +++++++++++++++
 libcpp/expr.cc                      | 28 +++++++++++++++++++++++-----
 2 files changed, 38 insertions(+), 5 deletions(-)

diff --git a/gcc/testsuite/g++.dg/cpp/pr119391.C 
b/gcc/testsuite/g++.dg/cpp/pr119391.C
new file mode 100644
index 000000000000..6e70efc0b465
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp/pr119391.C
@@ -0,0 +1,15 @@
+// PR preprocessor/119391
+// { dg-do preprocess }
+// { dg-options "" }
+
+#if (1 << 63) != -9223372036854775807 - 1      // { dg-warning "integer 
overflow in preprocessor expression" "" { target c++98_only } }
+#warning "Unexpected value"
+#endif
+#if (3 << 62) != -4611686018427387904          // { dg-warning "integer 
overflow in preprocessor expression" "" { target c++98_only } }
+#warning "Unexpected value"
+#endif
+#if 1 << 64                                    // { dg-warning "integer 
overflow in preprocessor expression" }
+#endif
+#if (3 << 63) != -9223372036854775807 - 1      // { dg-warning "integer 
overflow in preprocessor expression" "" { target c++17_down } }
+#warning "Unexpected value"
+#endif
diff --git a/libcpp/expr.cc b/libcpp/expr.cc
index 4573752dd5d1..7bb57a340d85 100644
--- a/libcpp/expr.cc
+++ b/libcpp/expr.cc
@@ -53,7 +53,7 @@ static cpp_num num_equality_op (cpp_reader *, cpp_num, 
cpp_num,
 static cpp_num num_mul (cpp_reader *, cpp_num, cpp_num);
 static cpp_num num_div_op (cpp_reader *, cpp_num, cpp_num, enum cpp_ttype,
                           location_t);
-static cpp_num num_lshift (cpp_num, size_t, size_t);
+static cpp_num num_lshift (cpp_reader *, cpp_num, size_t, size_t);
 static cpp_num num_rshift (cpp_num, size_t, size_t);
 
 static cpp_num append_digit (cpp_num, int, int, size_t);
@@ -2049,7 +2049,7 @@ num_rshift (cpp_num num, size_t precision, size_t n)
 
 /* Shift NUM, of width PRECISION, left by N bits.  */
 static cpp_num
-num_lshift (cpp_num num, size_t precision, size_t n)
+num_lshift (cpp_reader *pfile, cpp_num num, size_t precision, size_t n)
 {
   if (n >= precision)
     {
@@ -2075,8 +2075,26 @@ num_lshift (cpp_num num, size_t precision, size_t n)
        }
       num = num_trim (num, precision);
 
-      if (num.unsignedp)
+      if (num.unsignedp
+         /* For C++20 or later since P1236R1, there is no overflow for signed
+            left shifts, it is as if the shift was in uintmax_t and cast
+            back to intmax_t afterwards.  */
+         || (CPP_OPTION (pfile, cplusplus)
+             && CPP_OPTION (pfile, lang) >= CLK_GNUCXX20))
        num.overflow = false;
+      else if (CPP_OPTION (pfile, cplusplus)
+              && CPP_OPTION (pfile, lang) >= CLK_GNUCXX11
+              && num_positive (orig, precision))
+       {
+         /* For C++11 - C++17 since CWG1457, 1 << 63 is allowed because it is
+            representable in uintmax_t, but 3 << 63 is not.
+            Test whether num >> (precision - 1 - n) as logical
+            shift is > 1.  */
+         maybe_orig = orig;
+         maybe_orig.unsignedp = true;
+         maybe_orig = num_rshift (maybe_orig, precision, precision - 1 - n);
+         num.overflow = maybe_orig.high || maybe_orig.low > 1;
+       }
       else
        {
          maybe_orig = num_rshift (num, precision, n);
@@ -2149,7 +2167,7 @@ num_binary_op (cpp_reader *pfile, cpp_num lhs, cpp_num 
rhs, enum cpp_ttype op)
       else
        n = rhs.low;
       if (op == CPP_LSHIFT)
-       lhs = num_lshift (lhs, precision, n);
+       lhs = num_lshift (pfile, lhs, precision, n);
       else
        lhs = num_rshift (lhs, precision, n);
       break;
@@ -2347,7 +2365,7 @@ num_div_op (cpp_reader *pfile, cpp_num lhs, cpp_num rhs, 
enum cpp_ttype op,
   rhs.unsignedp = true;
   lhs.unsignedp = true;
   i = precision - i - 1;
-  sub = num_lshift (rhs, precision, i);
+  sub = num_lshift (pfile, rhs, precision, i);
 
   result.high = result.low = 0;
   for (;;)

Reply via email to