aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile/internal/types2/expr.go
diff options
context:
space:
mode:
authorRobert Griesemer <gri@golang.org>2021-01-28 14:29:25 -0800
committerRobert Griesemer <gri@golang.org>2021-01-30 00:36:53 +0000
commita59cb5109d49ac0dc09337449b9c7760ecc66c0e (patch)
tree02ca11cbef50e9b41ab1448a68810e2014284841 /src/cmd/compile/internal/types2/expr.go
parent507e641963c6e4277ae4bc9d5f44469d2b4c9c8f (diff)
downloadgo-a59cb5109d49ac0dc09337449b9c7760ecc66c0e.tar.gz
go-a59cb5109d49ac0dc09337449b9c7760ecc66c0e.zip
[dev.typeparams] cmd/compile/internal/types2: handle untyped constant arithmetic overflow
Factor out the existing "constant representation" check after untyped constant arithmetic and combine with an overflow check. Use a better heuristic for determining the error position if we know the error is for a constant operand that is the result of an arithmetic expression. Related cleanups. With this change, untyped constant arithmetic reports an error when (integer) constants become too large (> 2048 bits). Before, such arithmetic was only limited by space and time. Change-Id: Id3cea66c8ba697ff4c7fd1e848f350d9713e3c75 Reviewed-on: https://go-review.googlesource.com/c/go/+/287832 Trust: Robert Griesemer <gri@golang.org> Run-TryBot: Robert Griesemer <gri@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
Diffstat (limited to 'src/cmd/compile/internal/types2/expr.go')
-rw-r--r--src/cmd/compile/internal/types2/expr.go145
1 files changed, 88 insertions, 57 deletions
diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go
index 3378c606ad..c66e115c1f 100644
--- a/src/cmd/compile/internal/types2/expr.go
+++ b/src/cmd/compile/internal/types2/expr.go
@@ -83,13 +83,67 @@ func (check *Checker) op(m opPredicates, x *operand, op syntax.Operator) bool {
return true
}
-// The unary expression e may be nil. It's passed in for better error messages only.
-func (check *Checker) unary(x *operand, e *syntax.Operation, op syntax.Operator) {
- switch op {
+// overflow checks that the constant x is representable by its type.
+// For untyped constants, it checks that the value doesn't become
+// arbitrarily large.
+func (check *Checker) overflow(x *operand) {
+ assert(x.mode == constant_)
+
+ // If the corresponding expression is an operation, use the
+ // operator position rather than the start of the expression
+ // as error position.
+ pos := startPos(x.expr)
+ what := "" // operator description, if any
+ if op, _ := x.expr.(*syntax.Operation); op != nil {
+ pos = op.Pos()
+ if int(op.Op) < len(op2str) {
+ what = op2str[op.Op]
+ }
+ }
+
+ if x.val.Kind() == constant.Unknown {
+ // TODO(gri) We should report exactly what went wrong. At the
+ // moment we don't have the (go/constant) API for that.
+ // See also TODO in go/constant/value.go.
+ check.errorf(pos, "constant result is not representable")
+ return
+ }
+
+ // Typed constants must be representable in
+ // their type after each constant operation.
+ if isTyped(x.typ) {
+ check.representable(x, x.typ.Basic())
+ return
+ }
+
+ // Untyped integer values must not grow arbitrarily.
+ const limit = 4 * 512 // 512 is the constant precision - we need more because old tests had no limits
+ if x.val.Kind() == constant.Int && constant.BitLen(x.val) > limit {
+ check.errorf(pos, "constant %s overflow", what)
+ x.val = constant.MakeUnknown()
+ }
+}
+
+// This is only used for operations that may cause overflow.
+var op2str = [...]string{
+ syntax.Add: "addition",
+ syntax.Sub: "subtraction",
+ syntax.Xor: "bitwise XOR",
+ syntax.Mul: "multiplication",
+ syntax.Shl: "shift",
+}
+
+func (check *Checker) unary(x *operand, e *syntax.Operation) {
+ check.expr(x, e.X)
+ if x.mode == invalid {
+ return
+ }
+
+ switch e.Op {
case syntax.And:
// spec: "As an exception to the addressability
// requirement x may also be a composite literal."
- if _, ok := unparen(x.expr).(*syntax.CompositeLit); !ok && x.mode != variable {
+ if _, ok := unparen(e.X).(*syntax.CompositeLit); !ok && x.mode != variable {
check.invalidOpf(x, "cannot take address of %s", x)
x.mode = invalid
return
@@ -116,26 +170,23 @@ func (check *Checker) unary(x *operand, e *syntax.Operation, op syntax.Operator)
return
}
- if !check.op(unaryOpPredicates, x, op) {
+ if !check.op(unaryOpPredicates, x, e.Op) {
x.mode = invalid
return
}
if x.mode == constant_ {
- typ := x.typ.Basic()
+ if x.val.Kind() == constant.Unknown {
+ // nothing to do (and don't cause an error below in the overflow check)
+ return
+ }
var prec uint
- if isUnsigned(typ) {
- prec = uint(check.conf.sizeof(typ) * 8)
- }
- x.val = constant.UnaryOp(op2tok[op], x.val, prec)
- // Typed constants must be representable in
- // their type after each constant operation.
- if isTyped(typ) {
- if e != nil {
- x.expr = e // for better error message
- }
- check.representable(x, typ)
+ if isUnsigned(x.typ) {
+ prec = uint(check.conf.sizeof(x.typ) * 8)
}
+ x.val = constant.UnaryOp(op2tok[e.Op], x.val, prec)
+ x.expr = e
+ check.overflow(x)
return
}
@@ -701,7 +752,8 @@ func (check *Checker) comparison(x, y *operand, op syntax.Operator) {
x.typ = Typ[UntypedBool]
}
-func (check *Checker) shift(x, y *operand, e *syntax.Operation, op syntax.Operator) {
+// If e != nil, it must be the shift expression; it may be nil for non-constant shifts.
+func (check *Checker) shift(x, y *operand, e syntax.Expr, op syntax.Operator) {
// TODO(gri) This function seems overly complex. Revisit.
var xval constant.Value
@@ -765,14 +817,8 @@ func (check *Checker) shift(x, y *operand, e *syntax.Operation, op syntax.Operat
}
// x is a constant so xval != nil and it must be of Int kind.
x.val = constant.Shift(xval, op2tok[op], uint(s))
- // Typed constants must be representable in
- // their type after each constant operation.
- if isTyped(x.typ) {
- if e != nil {
- x.expr = e // for better error message
- }
- check.representable(x, x.typ.Basic())
- }
+ x.expr = e
+ check.overflow(x)
return
}
@@ -833,9 +879,9 @@ var binaryOpPredicates = opPredicates{
syntax.OrOr: isBoolean,
}
-// The binary expression e may be nil. It's passed in for better error messages only.
-// TODO(gri) revisit use of e and opPos
-func (check *Checker) binary(x *operand, e *syntax.Operation, lhs, rhs syntax.Expr, op syntax.Operator, opPos syntax.Pos) {
+// If e != nil, it must be the binary expression; it may be nil for non-constant expressions
+// (when invoked for an assignment operation where the binary expression is implicit).
+func (check *Checker) binary(x *operand, e syntax.Expr, lhs, rhs syntax.Expr, op syntax.Operator) {
var y operand
check.expr(x, lhs)
@@ -906,31 +952,20 @@ func (check *Checker) binary(x *operand, e *syntax.Operation, lhs, rhs syntax.Ex
}
if x.mode == constant_ && y.mode == constant_ {
- xval := x.val
- yval := y.val
- typ := x.typ.Basic()
- // force integer division of integer operands
+ // if either x or y has an unknown value, the result is unknown
+ if x.val.Kind() == constant.Unknown || y.val.Kind() == constant.Unknown {
+ x.val = constant.MakeUnknown()
+ // x.typ is unchanged
+ return
+ }
+ // force integer division for integer operands
tok := op2tok[op]
- if op == syntax.Div && isInteger(typ) {
+ if op == syntax.Div && isInteger(x.typ) {
tok = token.QUO_ASSIGN
}
- x.val = constant.BinaryOp(xval, tok, yval)
- // report error if valid operands lead to an invalid result
- if xval.Kind() != constant.Unknown && yval.Kind() != constant.Unknown && x.val.Kind() == constant.Unknown {
- // TODO(gri) We should report exactly what went wrong. At the
- // moment we don't have the (go/constant) API for that.
- // See also TODO in go/constant/value.go.
- check.errorf(opPos, "constant result is not representable")
- // TODO(gri) Should we mark operands with unknown values as invalid?
- }
- // Typed constants must be representable in
- // their type after each constant operation.
- if isTyped(typ) {
- if e != nil {
- x.expr = e // for better error message
- }
- check.representable(x, typ)
- }
+ x.val = constant.BinaryOp(x.val, tok, y.val)
+ x.expr = e
+ check.overflow(x)
return
}
@@ -1722,11 +1757,7 @@ func (check *Checker) exprInternal(x *operand, e syntax.Expr, hint Type) exprKin
break
}
- check.expr(x, e.X)
- if x.mode == invalid {
- goto Error
- }
- check.unary(x, e, e.Op)
+ check.unary(x, e)
if x.mode == invalid {
goto Error
}
@@ -1738,7 +1769,7 @@ func (check *Checker) exprInternal(x *operand, e syntax.Expr, hint Type) exprKin
}
// binary expression
- check.binary(x, e, e.X, e.Y, e.Op, e.Y.Pos()) // TODO(gri) should have OpPos here (like in go/types)
+ check.binary(x, e, e.X, e.Y, e.Op)
if x.mode == invalid {
goto Error
}