]> Cypherpunks repositories - gostls13.git/commitdiff
go/types, go/constant: handle infinities as unknown values
authorRobert Griesemer <gri@golang.org>
Thu, 19 Nov 2020 20:40:19 +0000 (12:40 -0800)
committerRobert Griesemer <gri@golang.org>
Fri, 20 Nov 2020 00:09:05 +0000 (00:09 +0000)
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>
src/go/constant/value.go
src/go/constant/value_test.go
src/go/types/expr.go
src/go/types/fixedbugs/issue20583.src [new file with mode: 0644]
src/go/types/stmt.go

index 08bcb3bf8719d641ad12e31df4600ae5fa5ee0be..116c7575d9eeb1dd595805901b4434f7baef5969 100644 (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}
 }
 
index a319039fc6e89d7962666219d105d5e0f2549ce2..1a5025cbbd4920b5115dfbeec1ec3b5f0417074b 100644 (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`,
index b026e99ce28356dda880ff54ccdf0614451dc896..5bf9c814603cbe4e488679527795786ee48fba31 100644 (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
                }
diff --git a/src/go/types/fixedbugs/issue20583.src b/src/go/types/fixedbugs/issue20583.src
new file mode 100644 (file)
index 0000000..d26dbad
--- /dev/null
@@ -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
+)
index b1ccbf0c65d4d53b38719e522b73ee8207ea7152..7b3f322ced2c5763597d110ae607edbf094d57bf 100644 (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
                        }