aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/testsuite/gdb.base/bitshift.exp368
-rw-r--r--gdb/valarith.c103
2 files changed, 467 insertions, 4 deletions
diff --git a/gdb/testsuite/gdb.base/bitshift.exp b/gdb/testsuite/gdb.base/bitshift.exp
new file mode 100644
index 0000000..03f18b9
--- /dev/null
+++ b/gdb/testsuite/gdb.base/bitshift.exp
@@ -0,0 +1,368 @@
+# Copyright 2022 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test left and right bit shifting, in all languages that have such
+# operator.
+
+clean_restart
+
+# Test a print command that prints out RESULT_RE. If WARNING_OR_ERROR
+# is non-empty, it is expected that for languages other than Go, GDB
+# prints this warning before the print result. For Go, this is an
+# expected error. If WARNING_OR_ERROR is empty, it is expected that
+# GDB prints no text other than the print result.
+proc test_shift {lang cmd result_re {warning_or_error ""}} {
+ set cmd_re [string_to_regexp $cmd]
+
+ if {$lang == "go"} {
+ if {$warning_or_error != ""} {
+ set error_re "[string_to_regexp $warning_or_error]"
+ gdb_test_multiple $cmd "" {
+ -re -wrap "^$cmd_re\r\n$error_re" {
+ pass $gdb_test_name
+ }
+ }
+ } else {
+ gdb_test_multiple $cmd "" {
+ -re -wrap "^$cmd_re\r\n\\$$::decimal$result_re" {
+ pass $gdb_test_name
+ }
+ }
+ }
+ } else {
+ if {$warning_or_error != ""} {
+ set warning_re "warning: [string_to_regexp $warning_or_error]\r\n"
+ } else {
+ set warning_re ""
+ }
+
+ gdb_test_multiple $cmd "" {
+ -re -wrap "^$cmd_re\r\n$warning_re\\$$::decimal$result_re" {
+ pass $gdb_test_name
+ }
+ }
+ }
+}
+
+# Some warnings/errors GDB outputs.
+set rs_negative_shift_count "right shift count is negative"
+set rs_too_large_shift_count "right shift count >= width of type"
+set ls_negative_shift_count "left shift count is negative"
+set ls_too_large_shift_count "left shift count >= width of type"
+
+# Test a left shift that results in a too-large shift count warning in
+# all languages except Go.
+proc test_lshift_tl {lang cmd result_re} {
+ if {$lang != "go"} {
+ test_shift $lang $cmd $result_re $::ls_too_large_shift_count
+ } else {
+ test_shift $lang $cmd $result_re
+ }
+}
+
+# Test a right shift that results in a too-large shift count warning
+# in all languages except Go.
+proc test_rshift_tl {lang cmd result_re} {
+ if {$lang != "go"} {
+ test_shift $lang $cmd $result_re $::rs_too_large_shift_count
+ } else {
+ test_shift $lang $cmd $result_re
+ }
+}
+
+# Return VAL, an integer value converted/cast to the right type for
+# LANG. SIGNED indicates whether the type should be signed or
+# unsigned. BITS indicates the bit width of the type. E.g., signed=0
+# and bits=32 results in:
+# Go => "uint($VAL)"
+# D => "cast(uint) $VAL"
+# Rust => "$VAL as i32"
+# C/C++/others => "(unsigned int) $VAL"
+proc make_val_cast {lang signed bits val} {
+ if {$lang == "go"} {
+ if {$signed} {
+ set sign_prefix ""
+ } else {
+ set sign_prefix "u"
+ }
+ return "${sign_prefix}int${bits}($val)"
+ } elseif {$lang == "d"} {
+ if {$signed} {
+ set sign_prefix ""
+ } else {
+ set sign_prefix "u"
+ }
+ if {$bits == 8} {
+ set type "byte"
+ } elseif {$bits == 16} {
+ set type "short"
+ } elseif {$bits == 32} {
+ set type "int"
+ } elseif {$bits == 64} {
+ set type "long"
+ } else {
+ error "$lang: unsupported bits"
+ }
+ return "cast(${sign_prefix}$type) $val"
+ } elseif {$lang == "rust"} {
+ if {$signed} {
+ set sign_prefix "i"
+ } else {
+ set sign_prefix "u"
+ }
+ return "$val as ${sign_prefix}$bits"
+ } else {
+ # C-like cast.
+ if {$signed} {
+ set sign_prefix ""
+ } else {
+ set sign_prefix "un"
+ }
+ if {$bits == 8} {
+ set type "char"
+ } elseif {$bits == 16} {
+ set type "short"
+ } elseif {$bits == 32} {
+ set type "int"
+ } elseif {$bits == 64} {
+ if {$lang == "opencl"} {
+ set type "long"
+ } else {
+ set type "long long"
+ }
+ } else {
+ error "$lang: unsupported bits"
+ }
+ return "(${sign_prefix}signed $type) $val"
+ }
+}
+
+# Generate make_int8 ... make_uint64 convenience procs, wrappers
+# around make_val_cast.
+foreach signed {0 1} {
+ if {$signed} {
+ set sign_prefix ""
+ } else {
+ set sign_prefix "u"
+ }
+ foreach bits {8 16 32 64} {
+ proc make_${sign_prefix}int${bits} {lang val} \
+ "make_val_cast \$lang $signed $bits \$val"
+ }
+}
+
+# Test bitshifting, particularly with negative shift counts and
+# too-large-for-type shift counts. Exercises all C-like-ish
+# languages.
+proc test_shifts {} {
+ global ls_negative_shift_count rs_negative_shift_count
+
+ # Extract the set of all supported languages. We try all except
+ # languages we know wouldn't work. We do this instead of
+ # hardcoding the set of languages that we know work, so that if
+ # GDB gains a new language, it is automatically exercised.
+ set supported_langs [get_set_option_choices "set language"]
+
+ foreach_with_prefix lang $supported_langs {
+ set skip_langs {
+ "unknown" "ada" "modula-2" "pascal" "fortran"
+ }
+ if {[lsearch -exact $skip_langs $lang] >= 0} {
+ continue
+ }
+
+ gdb_test_no_output "set language $lang"
+
+ # Make sure a signed left shift that overflows, i.e., whose
+ # result isn't representable in the signed type of the lhs,
+ # which is actually undefined, doesn't crash GDB when is it
+ # built with UBSan.
+
+ with_test_prefix "lsh overflow" {
+ test_shift $lang "print /x 0x0fffffffffffffff << 8" \
+ " = 0xffffffffffffff00"
+ test_shift $lang "print /x 0x0fffffff << 8" \
+ " = 0xffffff00"
+
+ # Make sure the result is still signed when the lhs was
+ # signed.
+ test_shift $lang "print 0x0fffffffffffffff << 8" " = -256"
+ test_shift $lang "print 0x0fffffff << 8" " = -256"
+ }
+
+ # 8-bit and 16-bit are promoted to int.
+ with_test_prefix "8-bit, promoted" {
+ foreach lhs \
+ [list \
+ [make_int8 $lang 0x0f] \
+ [make_uint8 $lang 0x0f]] \
+ {
+ test_shift $lang "print /x $lhs << 8" " = 0xf00"
+ test_shift $lang "print $lhs << 8" " = 3840"
+ }
+ }
+ with_test_prefix "16-bit, promoted" {
+ foreach lhs \
+ [list \
+ [make_int16 $lang 0x0fff] \
+ [make_uint16 $lang 0x0fff]] \
+ {
+ test_shift $lang "print /x $lhs << 8" " = 0xfff00"
+ test_shift $lang "print $lhs << 8" " = 1048320"
+ }
+ }
+
+ # Similarly, test shifting with both negative and too-large
+ # rhs. Both cases are undefined, but GDB lets them go through
+ # anyhow, similarly to how compilers don't error out. Try
+ # both signed and unsigned lhs.
+
+ # 8-bit lhs, signed and unsigned. These get promoted to
+ # 32-bit int.
+ with_test_prefix "8-bit, invalid" {
+ foreach lhs \
+ [list \
+ [make_int8 $lang 0x7f] \
+ [make_uint8 $lang 0xff]] \
+ {
+ test_shift $lang "print $lhs << -1" " = 0" \
+ $ls_negative_shift_count
+ test_shift $lang "print $lhs >> -1" " = 0" \
+ $rs_negative_shift_count
+
+ test_shift $lang "print/x $lhs << 8" " = 0x(7|f)f00"
+ test_shift $lang "print/x $lhs >> 8" " = 0x0"
+
+ test_lshift_tl $lang "print $lhs << 32" " = 0"
+ test_rshift_tl $lang "print $lhs >> 32" " = 0"
+ test_lshift_tl $lang "print $lhs << 33" " = 0"
+ test_rshift_tl $lang "print $lhs >> 33" " = 0"
+ }
+ }
+
+ # 16-bit lhs, signed and unsigned. These get promoted to 32-bit int.
+ with_test_prefix "16-bit, invalid" {
+ foreach {lhs res} \
+ [list \
+ [make_int16 $lang 0x7fff] 0x7fff \
+ [make_uint16 $lang 0xffff] 0xffff] \
+ {
+ test_shift $lang "print $lhs << -1" " = 0" \
+ $ls_negative_shift_count
+ test_shift $lang "print $lhs >> -1" " = 0" \
+ $rs_negative_shift_count
+
+ # Confirm shifting by 0 doesn't warn.
+ test_shift $lang "print/x $lhs << 0" " = $res"
+ test_shift $lang "print/x $lhs >> 0" " = $res"
+
+ # These don't overflow due to promotion.
+ test_shift $lang "print/x $lhs << 16" " = 0x(7|f)fff0000"
+ test_shift $lang "print/x $lhs >> 16" " = 0x0"
+
+ test_lshift_tl $lang "print $lhs << 32" " = 0"
+ test_rshift_tl $lang "print $lhs >> 32" " = 0"
+ test_lshift_tl $lang "print $lhs << 33" " = 0"
+ test_rshift_tl $lang "print $lhs >> 33" " = 0"
+ }
+ }
+
+ # 32-bit lhs, signed and unsigned.
+ with_test_prefix "32-bit, invalid" {
+ foreach {lhs res} \
+ [list \
+ [make_int32 $lang 0x7fffffff] 0x7fffffff \
+ [make_uint32 $lang 0xffffffff] 0xffffffff] \
+ {
+ test_shift $lang "print $lhs << -1" " = 0" \
+ $ls_negative_shift_count
+ test_shift $lang "print $lhs >> -1" " = 0" \
+ $rs_negative_shift_count
+
+ # Confirm shifting by 0 doesn't warn.
+ test_shift $lang "print/x $lhs << 0" " = $res"
+ test_shift $lang "print/x $lhs >> 0" " = $res"
+
+ test_lshift_tl $lang "print $lhs << 32" " = 0"
+ test_rshift_tl $lang "print $lhs >> 32" " = 0"
+
+ test_lshift_tl $lang "print $lhs << 33" " = 0"
+ test_rshift_tl $lang "print $lhs >> 33" " = 0"
+ }
+ }
+
+ # 64-bit lhs, signed and unsigned.
+ with_test_prefix "64-bit, invalid" {
+ foreach {lhs res} \
+ [list \
+ [make_int64 $lang 0x7fffffffffffffff] \
+ 0x7fffffffffffffff \
+ \
+ [make_uint64 $lang 0xffffffffffffffff] \
+ 0xffffffffffffffff] \
+ {
+ test_shift $lang "print $lhs << -1" " = 0" \
+ $ls_negative_shift_count
+ test_shift $lang "print $lhs >> -1" " = 0" \
+ $rs_negative_shift_count
+
+ # Confirm shifting by 0 doesn't warn.
+ test_shift $lang "print/x $lhs << 0" " = $res"
+ test_shift $lang "print/x $lhs >> 0" " = $res"
+
+ test_lshift_tl $lang "print $lhs << 64" " = 0"
+ test_rshift_tl $lang "print $lhs >> 64" " = 0"
+
+ test_lshift_tl $lang "print $lhs << 65" " = 0"
+ test_rshift_tl $lang "print $lhs >> 65" " = 0"
+ }
+ }
+
+ # Right shift a negative number by a negative amount.
+ with_test_prefix "neg lhs/rhs" {
+ test_shift $lang "print -1 >> -1" " = -1" $rs_negative_shift_count
+ test_shift $lang "print -4 >> -2" " = -1" $rs_negative_shift_count
+ }
+
+ # Check right shifting a negative value. For C++, this is
+ # implementation-defined, up until C++20. In most
+ # implementations, this performs an arithmetic right shift, so
+ # that the result remains negative. Currently, GDB does
+ # whatever the host's compiler does. If that turns out wrong
+ # for some host/target, then GDB should be taught to ask the
+ # target gdbarch what to do.
+ with_test_prefix "rsh neg lhs" {
+ test_shift $lang "print -1 >> 0" " = -1"
+ test_shift $lang "print -1 >> 1" " = -1"
+ test_shift $lang "print -8 >> 1" " = -4"
+ test_shift $lang "print [make_int64 $lang -8] >> 1" " = -4"
+ }
+
+ # Make sure an unsigned 64-bit value with high bit set isn't
+ # confused for a negative shift count in the warning messages.
+ with_test_prefix "max-uint64" {
+ test_lshift_tl $lang \
+ "print 1 << [make_uint64 $lang 0xffffffffffffffff]" " = 0"
+ test_rshift_tl $lang \
+ "print 1 >> [make_uint64 $lang 0xffffffffffffffff]" " = 0"
+ test_lshift_tl $lang \
+ "print -1 << [make_uint64 $lang 0xffffffffffffffff]" " = 0"
+ test_rshift_tl $lang \
+ "print -1 >> [make_uint64 $lang 0xffffffffffffffff]" " = -1"
+ }
+ }
+}
+
+test_shifts
diff --git a/gdb/valarith.c b/gdb/valarith.c
index 36d30f1..6210267 100644
--- a/gdb/valarith.c
+++ b/gdb/valarith.c
@@ -1070,6 +1070,66 @@ complex_binop (struct value *arg1, struct value *arg2, enum exp_opcode op)
return value_literal_complex (result_real, result_imag, result_type);
}
+/* Return the type's length in bits. */
+
+static int
+type_length_bits (type *type)
+{
+ int unit_size = gdbarch_addressable_memory_unit_size (type->arch ());
+ return unit_size * 8 * TYPE_LENGTH (type);
+}
+
+/* Check whether the RHS value of a shift is valid in C/C++ semantics.
+ SHIFT_COUNT is the shift amount, SHIFT_COUNT_TYPE is the type of
+ the shift count value, used to determine whether the type is
+ signed, and RESULT_TYPE is the result type. This is used to avoid
+ both negative and too-large shift amounts, which are undefined, and
+ would crash a GDB built with UBSan. Depending on the current
+ language, if the shift is not valid, this either warns and returns
+ false, or errors out. Returns true if valid. */
+
+static bool
+check_valid_shift_count (int op, type *result_type,
+ type *shift_count_type, ULONGEST shift_count)
+{
+ if (!shift_count_type->is_unsigned () && (LONGEST) shift_count < 0)
+ {
+ auto error_or_warning = [] (const char *msg)
+ {
+ /* Shifts by a negative amount are always an error in Go. Other
+ languages are more permissive and their compilers just warn or
+ have modes to disable the errors. */
+ if (current_language->la_language == language_go)
+ error (("%s"), msg);
+ else
+ warning (("%s"), msg);
+ };
+
+ if (op == BINOP_RSH)
+ error_or_warning (_("right shift count is negative"));
+ else
+ error_or_warning (_("left shift count is negative"));
+ return false;
+ }
+
+ if (shift_count >= type_length_bits (result_type))
+ {
+ /* In Go, shifting by large amounts is defined. Be silent and
+ still return false, as the caller's error path does the right
+ thing for Go. */
+ if (current_language->la_language != language_go)
+ {
+ if (op == BINOP_RSH)
+ warning (_("right shift count >= width of type"));
+ else
+ warning (_("left shift count >= width of type"));
+ }
+ return false;
+ }
+
+ return true;
+}
+
/* Perform a binary operation on two operands which have reasonable
representations as integers or floats. This includes booleans,
characters, integers, or floats.
@@ -1233,11 +1293,17 @@ scalar_binop (struct value *arg1, struct value *arg2, enum exp_opcode op)
break;
case BINOP_LSH:
- v = v1 << v2;
+ if (!check_valid_shift_count (op, result_type, type2, v2))
+ v = 0;
+ else
+ v = v1 << v2;
break;
case BINOP_RSH:
- v = v1 >> v2;
+ if (!check_valid_shift_count (op, result_type, type2, v2))
+ v = 0;
+ else
+ v = v1 >> v2;
break;
case BINOP_BITWISE_AND:
@@ -1362,11 +1428,40 @@ scalar_binop (struct value *arg1, struct value *arg2, enum exp_opcode op)
break;
case BINOP_LSH:
- v = v1 << v2;
+ if (!check_valid_shift_count (op, result_type, type2, v2))
+ v = 0;
+ else
+ {
+ /* Cast to unsigned to avoid undefined behavior on
+ signed shift overflow (unless C++20 or later),
+ which would crash GDB when built with UBSan.
+ Note we don't warn on left signed shift overflow,
+ because starting with C++20, that is actually
+ defined behavior. Also, note GDB assumes 2's
+ complement throughout. */
+ v = (ULONGEST) v1 << v2;
+ }
break;
case BINOP_RSH:
- v = v1 >> v2;
+ if (!check_valid_shift_count (op, result_type, type2, v2))
+ {
+ /* Pretend the too-large shift was decomposed in a
+ number of smaller shifts. An arithmetic signed
+ right shift of a negative number always yields -1
+ with such semantics. This is the right thing to
+ do for Go, and we might as well do it for
+ languages where it is undefined. Also, pretend a
+ shift by a negative number was a shift by the
+ negative number cast to unsigned, which is the
+ same as shifting by a too-large number. */
+ if (v1 < 0)
+ v = -1;
+ else
+ v = 0;
+ }
+ else
+ v = v1 >> v2;
break;
case BINOP_BITWISE_AND: