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]>
Tested on x86_64-pc-linux-gnu.
---
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 65a7c8c1773..33a7d2ad719 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 c1f6002e816..b3295a54f36 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 00000000000..4745fcc26da
--- /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 00000000000..194f492fe1e
--- /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 1647f9157de..292a86e40fd 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 0a093caa4fb..2d4f0b95a8b 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]
--
2.55.0