1
0
mirror of https://github.com/golang/go synced 2024-11-26 08:58:09 -07:00

go/types, go/constant: handle infinities as unknown values

With this change, constant literals (and results of constant
operations) that internally become infinities are represented
externally (to go/constant) as "unknown" values.

The language has no provisions to deal with infinite constants,
and producing unknown values allows the typechecker to report
errors and avoid invalid operations (such as multiplication of
zero with infinity).

Fixes #20583.

Change-Id: I12f36a17d262ff7957b0d3880241b5a8b2984777
Reviewed-on: https://go-review.googlesource.com/c/go/+/271706
Trust: Robert Griesemer <gri@golang.org>
Run-TryBot: Robert Griesemer <gri@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
Robert Griesemer 2020-11-19 12:40:19 -08:00
parent f3ce010b33
commit 7eed73f36f
5 changed files with 66 additions and 10 deletions

View File

@ -66,6 +66,11 @@ type Value interface {
// The spec requires at least 256 bits; typical implementations use 512 bits.
const prec = 512
// TODO(gri) Consider storing "error" information in an unknownVal so clients
// can provide better error messages. For instance, if a number is
// too large (incl. infinity), that could be recorded in unknownVal.
// See also #20583 and #42695 for use cases.
type (
unknownVal struct{}
boolVal bool
@ -297,10 +302,16 @@ func makeFloat(x *big.Float) Value {
if x.Sign() == 0 {
return floatVal0
}
if x.IsInf() {
return unknownVal{}
}
return floatVal{x}
}
func makeComplex(re, im Value) Value {
if re.Kind() == Unknown || im.Kind() == Unknown {
return unknownVal{}
}
return complexVal{re, im}
}

View File

@ -82,6 +82,11 @@ var floatTests = []string{
`1_2_3.123 = 123.123`,
`0123.01_23 = 123.0123`,
`1e-1000000000 = 0`,
`1e+1000000000 = ?`,
`6e5518446744 = ?`,
`-6e5518446744 = ?`,
// hexadecimal floats
`0x0.p+0 = 0.`,
`0Xdeadcafe.p-10 = 0xdeadcafe/1024`,
@ -117,6 +122,11 @@ var imagTests = []string{
`0.e+1i = 0i`,
`123.E-1_0i = 123e-10i`,
`01_23.e123i = 123e123i`,
`1e-1000000000i = 0i`,
`1e+1000000000i = ?`,
`6e5518446744i = ?`,
`-6e5518446744i = ?`,
}
func testNumbers(t *testing.T, kind token.Token, tests []string) {
@ -129,21 +139,32 @@ func testNumbers(t *testing.T, kind token.Token, tests []string) {
x := MakeFromLiteral(a[0], kind, 0)
var y Value
if i := strings.Index(a[1], "/"); i >= 0 && kind == token.FLOAT {
n := MakeFromLiteral(a[1][:i], token.INT, 0)
d := MakeFromLiteral(a[1][i+1:], token.INT, 0)
y = BinaryOp(n, token.QUO, d)
if a[1] == "?" {
y = MakeUnknown()
} else {
y = MakeFromLiteral(a[1], kind, 0)
if i := strings.Index(a[1], "/"); i >= 0 && kind == token.FLOAT {
n := MakeFromLiteral(a[1][:i], token.INT, 0)
d := MakeFromLiteral(a[1][i+1:], token.INT, 0)
y = BinaryOp(n, token.QUO, d)
} else {
y = MakeFromLiteral(a[1], kind, 0)
}
if y.Kind() == Unknown {
panic(fmt.Sprintf("invalid test case: %s %d", test, y.Kind()))
}
}
xk := x.Kind()
yk := y.Kind()
if xk != yk || xk == Unknown {
if xk != yk {
t.Errorf("%s: got kind %d != %d", test, xk, yk)
continue
}
if yk == Unknown {
continue
}
if !Compare(x, token.EQL, y) {
t.Errorf("%s: %s != %s", test, x, y)
}
@ -200,6 +221,7 @@ var opTests = []string{
`1i * 1i = -1`,
`? * 0 = ?`,
`0 * ? = ?`,
`0 * 1e+1000000000 = ?`,
`0 / 0 = "division_by_zero"`,
`10 / 2 = 5`,
@ -207,6 +229,7 @@ var opTests = []string{
`5i / 3i = 5/3`,
`? / 0 = ?`,
`0 / ? = ?`,
`0 * 1e+1000000000i = ?`,
`0 % 0 = "runtime_error:_integer_divide_by_zero"`, // TODO(gri) should be the same as for /
`10 % 3 = 1`,

View File

@ -802,7 +802,7 @@ var binaryOpPredicates = opPredicates{
}
// The binary expression e may be nil. It's passed in for better error messages only.
func (check *Checker) binary(x *operand, e *ast.BinaryExpr, lhs, rhs ast.Expr, op token.Token) {
func (check *Checker) binary(x *operand, e *ast.BinaryExpr, lhs, rhs ast.Expr, op token.Token, opPos token.Pos) {
var y operand
check.expr(x, lhs)
@ -885,6 +885,14 @@ func (check *Checker) binary(x *operand, e *ast.BinaryExpr, lhs, rhs ast.Expr, o
op = token.QUO_ASSIGN
}
x.val = constant.BinaryOp(xval, op, 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(atPos(e.OpPos), _InvalidConstVal, "constant result 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) {
@ -1542,7 +1550,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
}
case *ast.BinaryExpr:
check.binary(x, e, e.X, e.Y, e.Op)
check.binary(x, e, e.X, e.Y, e.Op, e.OpPos)
if x.mode == invalid {
goto Error
}

View File

@ -0,0 +1,14 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package issue20583
const (
_ = 6e886451608 /* ERROR malformed constant */ /2
_ = 6e886451608i /* ERROR malformed constant */ /2
_ = 0 * 1e+1000000000 // ERROR malformed constant
x = 1e100000000
_ = x*x*x*x*x*x* /* ERROR not representable */ x
)

View File

@ -391,7 +391,7 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
}
Y := &ast.BasicLit{ValuePos: s.X.Pos(), Kind: token.INT, Value: "1"} // use x's position
check.binary(&x, nil, s.X, Y, op)
check.binary(&x, nil, s.X, Y, op, s.TokPos)
if x.mode == invalid {
return
}
@ -423,7 +423,7 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
return
}
var x operand
check.binary(&x, nil, s.Lhs[0], s.Rhs[0], op)
check.binary(&x, nil, s.Lhs[0], s.Rhs[0], op, s.TokPos)
if x.mode == invalid {
return
}