Skip to content

Commit acdddf6

Browse files
committed
go/ssa: allows right operand of a shift to be signed.
Removes the forced conversion of the right operand of a shift to an unsigned type. This allows for clients to correctly model the runtime panic when a signed shift count is negative. Fixes golang/go#51363 Change-Id: If59000eeb503fd45cdc6d4143dcc249242e7a957 Reviewed-on: https://go-review.googlesource.com/c/tools/+/387995 Trust: Tim King <[email protected]> Run-TryBot: Tim King <[email protected]> gopls-CI: kokoro <[email protected]> Reviewed-by: Zvonimir Pavlinovic <[email protected]> Reviewed-by: Dominik Honnef <[email protected]> Trust: Dominik Honnef <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 9ffa3ad commit acdddf6

File tree

4 files changed

+79
-5
lines changed

4 files changed

+79
-5
lines changed

go/ssa/emit.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,16 @@ func emitArith(f *Function, op token.Token, x, y Value, t types.Type, pos token.
7474
case token.SHL, token.SHR:
7575
x = emitConv(f, x, t)
7676
// y may be signed or an 'untyped' constant.
77-
// TODO(adonovan): whence signed values?
78-
if b, ok := y.Type().Underlying().(*types.Basic); ok && b.Info()&types.IsUnsigned == 0 {
79-
y = emitConv(f, y, types.Typ[types.Uint64])
77+
78+
// There is a runtime panic if y is signed and <0. Instead of inserting a check for y<0
79+
// and converting to an unsigned value (like the compiler) leave y as is.
80+
81+
if b, ok := y.Type().Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
82+
// Untyped conversion:
83+
// Spec https://go.dev/ref/spec#Operators:
84+
// The right operand in a shift expression must have integer type or be an untyped constant
85+
// representable by a value of type uint.
86+
y = emitConv(f, y, types.Typ[types.Uint])
8087
}
8188

8289
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT:

go/ssa/interp/interp_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ var gorootTestTests = []string{
109109
var testdataTests = []string{
110110
"boundmeth.go",
111111
"complit.go",
112+
"convert.go",
112113
"coverage.go",
113114
"defer.go",
114115
"fieldprom.go",

go/ssa/interp/ops.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,26 @@ func asUint64(x value) uint64 {
137137
panic(fmt.Sprintf("cannot convert %T to uint64", x))
138138
}
139139

140+
// asUnsigned returns the value of x, which must be an integer type, as its equivalent unsigned type,
141+
// and returns true if x is non-negative.
142+
func asUnsigned(x value) (value, bool) {
143+
switch x := x.(type) {
144+
case int:
145+
return uint(x), x >= 0
146+
case int8:
147+
return uint8(x), x >= 0
148+
case int16:
149+
return uint16(x), x >= 0
150+
case int32:
151+
return uint32(x), x >= 0
152+
case int64:
153+
return uint64(x), x >= 0
154+
case uint, uint8, uint32, uint64, uintptr:
155+
return x, true
156+
}
157+
panic(fmt.Sprintf("cannot convert %T to unsigned", x))
158+
}
159+
140160
// zero returns a new "zero" value of the specified type.
141161
func zero(t types.Type) value {
142162
switch t := t.(type) {
@@ -576,7 +596,11 @@ func binop(op token.Token, t types.Type, x, y value) value {
576596
}
577597

578598
case token.SHL:
579-
y := asUint64(y)
599+
u, ok := asUnsigned(y)
600+
if !ok {
601+
panic("negative shift amount")
602+
}
603+
y := asUint64(u)
580604
switch x.(type) {
581605
case int:
582606
return x.(int) << y
@@ -603,7 +627,11 @@ func binop(op token.Token, t types.Type, x, y value) value {
603627
}
604628

605629
case token.SHR:
606-
y := asUint64(y)
630+
u, ok := asUnsigned(y)
631+
if !ok {
632+
panic("negative shift amount")
633+
}
634+
y := asUint64(u)
607635
switch x.(type) {
608636
case int:
609637
return x.(int) >> y

go/ssa/interp/testdata/convert.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Test conversion operations.
6+
7+
package main
8+
9+
func left(x int) { _ = 1 << x }
10+
func right(x int) { _ = 1 >> x }
11+
12+
func main() {
13+
wantPanic(
14+
func() {
15+
left(-1)
16+
},
17+
"runtime error: negative shift amount",
18+
)
19+
wantPanic(
20+
func() {
21+
right(-1)
22+
},
23+
"runtime error: negative shift amount",
24+
)
25+
}
26+
27+
func wantPanic(fn func(), s string) {
28+
defer func() {
29+
err := recover()
30+
if err == nil {
31+
panic("expected panic")
32+
}
33+
if got := err.(error).Error(); got != s {
34+
panic("expected panic " + s + " got " + got)
35+
}
36+
}()
37+
fn()
38+
}

0 commit comments

Comments
 (0)