From: Utkarsh Bahuguna <[email protected]>

Rust rejects overflow in const eval for signed and unsigned alike, but
GCC only sets TREE_OVERFLOW for signed ops, so unsigned just wrapped
silently. The signed case did get caught, but only after folding, against
an INTEGER_CST that carries no location - hence the locationless error.

So build arithmetic with build2_loc instead of folding it right away. The
const folder then sees the operation and its operands, and can report
overflow on both signednesses with a proper location.

gcc/rust/ChangeLog:

        * rust-gcc.cc (arithmetic_or_logical_expression): Use build2_loc
        instead of fold_build2_loc.
        * backend/rust-constexpr.cc (eval_check_overflow_p): New function.
        (eval_binary_expression): Use it.

gcc/testsuite/ChangeLog:

        * rust/compile/const_overflow_unsigned.rs: Rewrite for E0080.
        * rust/compile/const_overflow_signed.rs: New test.
        * rust/compile/torture/arrays5.rs: Require lp64 target.
        * rust/compile/torture/arrays6.rs: Likewise.

Signed-off-by: Utkarsh Bahuguna <[email protected]>
---
This change was merged into the gccrs repository and is posted here for
upstream visibility and potential drive-by review, as requested by GCC
release managers.
Each commit email contains a link to its details on github from where you can
find the Pull-Request and associated discussions.


Commit on github: 
https://github.com/Rust-GCC/gccrs/commit/1f3ce07acdba539ad2387bd4b1f07bf2b1e8a52f

The commit has NOT been mentioned in any issue.

The commit has been mentioned in the following pull-request(s):
 - https://github.com/Rust-GCC/gccrs/pull/4930

 gcc/rust/backend/rust-constexpr.cc            | 77 +++++++++++++++++++
 gcc/rust/rust-gcc.cc                          |  2 +-
 .../rust/compile/const_overflow_signed.rs     | 21 +++++
 .../rust/compile/const_overflow_unsigned.rs   | 31 ++++++++
 gcc/testsuite/rust/compile/torture/arrays5.rs |  1 +
 gcc/testsuite/rust/compile/torture/arrays6.rs |  1 +
 6 files changed, 132 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/rust/compile/const_overflow_signed.rs
 create mode 100644 gcc/testsuite/rust/compile/const_overflow_unsigned.rs

diff --git a/gcc/rust/backend/rust-constexpr.cc 
b/gcc/rust/backend/rust-constexpr.cc
index 65a7c8c17..33a7d2ad7 100644
--- a/gcc/rust/backend/rust-constexpr.cc
+++ b/gcc/rust/backend/rust-constexpr.cc
@@ -22,6 +22,7 @@
 #include "realmpfr.h"
 #include "convert.h"
 #include "print-tree.h"
+#include "tree-pretty-print.h"
 #include "gimplify.h"
 #include "tree-iterator.h"
 #include "timevar.h"
@@ -548,6 +549,9 @@ static tree eval_unary_expression (const constexpr_ctx 
*ctx, tree t,
 static bool eval_check_shift_p (location_t loc, const constexpr_ctx *ctx,
                                enum tree_code code, tree type, tree lhs,
                                tree rhs);
+static bool eval_check_overflow_p (location_t loc, const constexpr_ctx *ctx,
+                                  enum tree_code code, tree type, tree lhs,
+                                  tree rhs);
 static tree fold_pointer_plus_expression (const constexpr_ctx *ctx, tree t,
                                          tree lhs, tree rhs,
                                          bool *non_constant_p,
@@ -3249,6 +3253,16 @@ eval_binary_expression (const constexpr_ctx *ctx, tree 
t, bool lval,
     }
   else if (eval_check_shift_p (loc, ctx, code, type, lhs, rhs))
     *non_constant_p = true;
+  else if (eval_check_overflow_p (loc, ctx, code, type, lhs, rhs))
+    {
+      *non_constant_p = true;
+      /* The overflow has been diagnosed here, with a location.  Clear the
+        flag so that the generic TREE_OVERFLOW check in
+        eval_constant_expression does not report it a second time against
+        the folded constant, which carries no location.  */
+      if (r != NULL_TREE && CONSTANT_CLASS_P (r))
+       TREE_OVERFLOW (r) = false;
+    }
   /* Don't VERIFY_CONSTANT if this might be dealing with a pointer to
      a local array in a constexpr function.  */
   bool ptr = INDIRECT_TYPE_P (TREE_TYPE (lhs));
@@ -6840,6 +6854,69 @@ eval_check_shift_p (location_t loc, const constexpr_ctx 
*ctx,
   return false;
 }
 
+/* Rust rejects arithmetic overflow during constant evaluation for both signed
+   and unsigned types, unlike C where unsigned wraparound is well defined, so
+   TREE_OVERFLOW alone is not enough: GCC only sets it for signed operations.
+
+   Operations built in a constant context are left unfolded by
+   Backend::arithmetic_or_logical_expression, so that we still see the
+   operation and its operands here rather than an INTEGER_CST which would
+   carry neither a location nor, for unsigned types, any trace of the
+   overflow.
+
+   Returns true when the operation overflows, in which case the expression is
+   not a constant.  */
+
+static bool
+eval_check_overflow_p (location_t loc, const constexpr_ctx *ctx,
+                      enum tree_code code, tree type, tree lhs, tree rhs)
+{
+  if ((code != PLUS_EXPR && code != MINUS_EXPR && code != MULT_EXPR)
+      || TREE_CODE (lhs) != INTEGER_CST || TREE_CODE (rhs) != INTEGER_CST
+      || !INTEGRAL_TYPE_P (type)
+      || TYPE_PRECISION (TREE_TYPE (lhs)) != TYPE_PRECISION (type)
+      || TYPE_PRECISION (TREE_TYPE (rhs)) != TYPE_PRECISION (type))
+    return false;
+
+  /* Note we deliberately do not check TYPE_OVERFLOW_WRAPS here: it is set for
+     unsigned types because wrapping is defined in C, but Rust rejects it.  */
+  signop sgn = TYPE_SIGN (type);
+  wide_int lw = wi::to_wide (lhs);
+  wide_int rw = wi::to_wide (rhs);
+  wi::overflow_type ovf = wi::OVF_NONE;
+
+  switch (code)
+    {
+    case PLUS_EXPR:
+      wi::add (lw, rw, sgn, &ovf);
+      break;
+    case MINUS_EXPR:
+      wi::sub (lw, rw, sgn, &ovf);
+      break;
+    case MULT_EXPR:
+      wi::mul (lw, rw, sgn, &ovf);
+      break;
+    default:
+      gcc_unreachable ();
+    }
+
+  if (ovf == wi::OVF_NONE)
+    return false;
+
+  /* Unlike the shift checks above this is a hard error, not a permerror:
+     overflow in a constant is always rejected by Rust.  */
+  if (!ctx->quiet)
+    {
+      char *expr_str
+       = print_generic_expr_to_str (build2_loc (loc, code, type, lhs, rhs));
+      rust_error_at (loc, ErrorCode::E0080,
+                    "attempt to compute %qs, which would overflow", expr_str);
+      free (expr_str);
+    }
+
+  return true;
+}
+
 /* Helper function for cxx_eval_binary_expression.  Try to optimize
    original POINTER_PLUS_EXPR T, LHS p+ RHS, return NULL_TREE if the
    generic folding should be used.  */
diff --git a/gcc/rust/rust-gcc.cc b/gcc/rust/rust-gcc.cc
index c1f6002e8..b3295a54f 100644
--- a/gcc/rust/rust-gcc.cc
+++ b/gcc/rust/rust-gcc.cc
@@ -1127,7 +1127,7 @@ arithmetic_or_logical_expression 
(ArithmeticOrLogicalOperator op, tree left,
        }
     }
 
-  ret = fold_build2_loc (location, tree_code, tree_type, left, right);
+  ret = build2_loc (location, tree_code, tree_type, left, right);
   TREE_CONSTANT (ret) = TREE_CONSTANT (left) & TREE_CONSTANT (right);
 
   // TODO: How do we handle floating point?
diff --git a/gcc/testsuite/rust/compile/const_overflow_signed.rs 
b/gcc/testsuite/rust/compile/const_overflow_signed.rs
new file mode 100644
index 000000000..4745fcc26
--- /dev/null
+++ b/gcc/testsuite/rust/compile/const_overflow_signed.rs
@@ -0,0 +1,21 @@
+#![feature(no_core)]
+#![no_core]
+
+const A: i8 = 127 + 1;
+// { dg-error {attempt to compute .127 \+ 1., which would overflow \[E0080\]} 
"" { target *-*-* } .-1 }
+
+const B: i8 = -128 - 1;
+// { dg-error {attempt to compute .-128 - 1., which would overflow \[E0080\]} 
"" { target *-*-* } .-1 }
+
+const C: i8 = 64 * 2;
+// { dg-error {attempt to compute .64 \* 2., which would overflow \[E0080\]} 
"" { target *-*-* } .-1 }
+
+const D: i64 = 9223372036854775807 + 1;
+// { dg-error {attempt to compute .9223372036854775807 \+ 1., which would 
overflow \[E0080\]} "" { target *-*-* } .-1 }
+
+const E: i8 = 127;
+const F: i8 = E + 1;
+// { dg-error {attempt to compute .127 \+ 1., which would overflow \[E0080\]} 
"" { target *-*-* } .-1 }
+
+// Does not overflow, must keep compiling.
+const G: i8 = 100 + 27;
diff --git a/gcc/testsuite/rust/compile/const_overflow_unsigned.rs 
b/gcc/testsuite/rust/compile/const_overflow_unsigned.rs
new file mode 100644
index 000000000..194f492fe
--- /dev/null
+++ b/gcc/testsuite/rust/compile/const_overflow_unsigned.rs
@@ -0,0 +1,31 @@
+#![feature(no_core)]
+#![no_core]
+
+const A: u8 = 255 + 1;
+// { dg-error {attempt to compute .255 \+ 1., which would overflow \[E0080\]} 
"" { target *-*-* } .-1 }
+
+const B: u8 = 0 - 1;
+// { dg-error {attempt to compute .0 - 1., which would overflow \[E0080\]} "" 
{ target *-*-* } .-1 }
+
+const C: u8 = 128 * 2;
+// { dg-error {attempt to compute .128 \* 2., which would overflow \[E0080\]} 
"" { target *-*-* } .-1 }
+
+const D: u64 = 18446744073709551615 + 1;
+// { dg-error {attempt to compute .18446744073709551615 \+ 1., which would 
overflow \[E0080\]} "" { target *-*-* } .-1 }
+
+// The operands of a named constant are evaluated before the operation, so the
+// overflow is still reported against the operation itself.
+const E: u8 = 255;
+const F: u8 = E + 1;
+// { dg-error {attempt to compute .255 \+ 1., which would overflow \[E0080\]} 
"" { target *-*-* } .-1 }
+
+static G: u8 = 255 + 1;
+// { dg-error {attempt to compute .255 \+ 1., which would overflow \[E0080\]} 
"" { target *-*-* } .-1 }
+
+// Does not overflow, must keep compiling.
+const H: u8 = 200 + 55;
+
+// Wrapping at run time is not a constant evaluation, nothing to diagnose here.
+fn runtime (x: u8, y: u8) -> u8 {
+  x + y
+}
diff --git a/gcc/testsuite/rust/compile/torture/arrays5.rs 
b/gcc/testsuite/rust/compile/torture/arrays5.rs
index 1647f9157..292a86e40 100644
--- a/gcc/testsuite/rust/compile/torture/arrays5.rs
+++ b/gcc/testsuite/rust/compile/torture/arrays5.rs
@@ -1,3 +1,4 @@
+// { dg-require-effective-target lp64 }
 #![feature(no_core)]
 #![no_core]
 
diff --git a/gcc/testsuite/rust/compile/torture/arrays6.rs 
b/gcc/testsuite/rust/compile/torture/arrays6.rs
index 0a093caa4..2d4f0b95a 100644
--- a/gcc/testsuite/rust/compile/torture/arrays6.rs
+++ b/gcc/testsuite/rust/compile/torture/arrays6.rs
@@ -1,3 +1,4 @@
+// { dg-require-effective-target lp64 }
 #![feature(no_core)]
 #![no_core]
 

base-commit: 0fed9b4dbafe137b2a52ef84be8f20fffb3512ca
-- 
2.55.0

Reply via email to