mirror of
https://github.com/golang/go
synced 2024-11-22 14:04:48 -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:
parent
f3ce010b33
commit
7eed73f36f
@ -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}
|
||||
}
|
||||
|
||||
|
@ -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`,
|
||||
|
@ -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
|
||||
}
|
||||
|
14
src/go/types/fixedbugs/issue20583.src
Normal file
14
src/go/types/fixedbugs/issue20583.src
Normal 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
|
||||
)
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user