diff options
author | Robert Griesemer <gri@golang.org> | 2021-01-28 14:29:25 -0800 |
---|---|---|
committer | Robert Griesemer <gri@golang.org> | 2021-01-30 00:36:53 +0000 |
commit | a59cb5109d49ac0dc09337449b9c7760ecc66c0e (patch) | |
tree | 02ca11cbef50e9b41ab1448a68810e2014284841 /src/cmd/compile/internal/types2/expr.go | |
parent | 507e641963c6e4277ae4bc9d5f44469d2b4c9c8f (diff) | |
download | go-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.go | 145 |
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 } |