https://gcc.gnu.org/g:ca33fb40100211b9ed8c2e686b46cb9025791930
commit r17-5170-gca33fb40100211b9ed8c2e686b46cb9025791930 Author: Utkarsh Bahuguna <[email protected]> Date: Mon Sep 21 18:12:21 2026 +0530 gccrs: Diagnose const arithmetic overflow with E0080 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]> Diff: --- 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(-) diff --git a/gcc/rust/backend/rust-constexpr.cc b/gcc/rust/backend/rust-constexpr.cc index 65a7c8c1773d..33a7d2ad719b 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 c1f6002e8163..b3295a54f36e 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 000000000000..4745fcc26da1 --- /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 000000000000..194f492fe1e0 --- /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 1647f9157de4..292a86e40fd2 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 0a093caa4fbe..2d4f0b95a8b1 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]
