]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.typeparams] cmd/compile: refuse excessively long constants
authorRobert Griesemer <gri@golang.org>
Tue, 2 Feb 2021 21:20:03 +0000 (13:20 -0800)
committerRobert Griesemer <gri@golang.org>
Wed, 3 Feb 2021 20:22:34 +0000 (20:22 +0000)
The compiler uses 512 bit of precision for untyped constant
arithmetic but didn't restrict the length of incoming constant
literals in any way, possibly opening the door for excessively
long constants that could bring compilation to a crawl.

Add a simple check that refuses excessively long constants.
Add test.

Change-Id: I797cb2a8e677b8da2864eb92d686d271ab8a004d
Reviewed-on: https://go-review.googlesource.com/c/go/+/289049
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>
src/cmd/compile/internal/noder/noder.go
src/cmd/compile/internal/types2/expr.go
test/const7.go [new file with mode: 0644]

index 1c38f1a934d0b71c3256bfb7f45a72fde9cfbac5..d692bf97aaf41b3674e2305df36b53e53c320541 100644 (file)
@@ -1455,6 +1455,20 @@ func (p *noder) basicLit(lit *syntax.BasicLit) constant.Value {
        switch lit.Kind {
        case syntax.IntLit, syntax.FloatLit, syntax.ImagLit:
                checkLangCompat(lit)
+               // The max. mantissa precision for untyped numeric values
+               // is 512 bits, or 4048 bits for each of the two integer
+               // parts of a fraction for floating-point numbers that are
+               // represented accurately in the go/constant package.
+               // Constant literals that are longer than this many bits
+               // are not meaningful; and excessively long constants may
+               // consume a lot of space and time for a useless conversion.
+               // Cap constant length with a generous upper limit that also
+               // allows for separators between all digits.
+               const limit = 10000
+               if len(lit.Value) > limit {
+                       p.errorAt(lit.Pos(), "excessively long constant: %s... (%d chars)", lit.Value[:10], len(lit.Value))
+                       return constant.MakeUnknown()
+               }
        }
 
        v := constant.MakeFromLiteral(lit.Value, tokenForLitKind[lit.Kind], 0)
index c66e115c1fc77bd52755385a7f0c05f35af2caeb..a1a626fb33d171aedb06eb8e42b2bfe1d9fe7820 100644 (file)
@@ -1154,6 +1154,23 @@ func (check *Checker) exprInternal(x *operand, e syntax.Expr, hint Type) exprKin
                if e.Bad {
                        goto Error // error reported during parsing
                }
+               switch e.Kind {
+               case syntax.IntLit, syntax.FloatLit, syntax.ImagLit:
+                       // The max. mantissa precision for untyped numeric values
+                       // is 512 bits, or 4048 bits for each of the two integer
+                       // parts of a fraction for floating-point numbers that are
+                       // represented accurately in the go/constant package.
+                       // Constant literals that are longer than this many bits
+                       // are not meaningful; and excessively long constants may
+                       // consume a lot of space and time for a useless conversion.
+                       // Cap constant length with a generous upper limit that also
+                       // allows for separators between all digits.
+                       const limit = 10000
+                       if len(e.Value) > limit {
+                               check.errorf(e, "excessively long constant: %s... (%d chars)", e.Value[:10], len(e.Value))
+                               goto Error
+                       }
+               }
                x.setConst(e.Kind, e.Value)
                if x.mode == invalid {
                        // The parser already establishes syntactic correctness.
diff --git a/test/const7.go b/test/const7.go
new file mode 100644 (file)
index 0000000..9ffd678
--- /dev/null
@@ -0,0 +1,77 @@
+// run
+
+// Copyright 2021 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.
+
+// Check that the compiler refuses excessively long constants.
+
+package main
+
+import (
+       "bytes"
+       "fmt"
+       "io/ioutil"
+       "log"
+       "os"
+       "os/exec"
+       "path/filepath"
+       "runtime"
+       "strings"
+)
+
+// testProg creates a package called name, with path dir/name.go,
+// which declares an untyped constant of the given length.
+// testProg compiles this package and checks for the absence or
+// presence of a constant literal error.
+func testProg(dir, name string, G_option, length int, ok bool) {
+       var buf bytes.Buffer
+
+       fmt.Fprintf(&buf,
+               "package %s; const _ = %s // %d digits",
+               name, strings.Repeat("9", length), length,
+       )
+
+       filename := filepath.Join(dir, fmt.Sprintf("%s.go", name))
+       if err := os.WriteFile(filename, buf.Bytes(), 0666); err != nil {
+               log.Fatal(err)
+       }
+
+       cmd := exec.Command("go", "tool", "compile", fmt.Sprintf("-G=%d", G_option), filename)
+       cmd.Dir = dir
+       output, err := cmd.CombinedOutput()
+
+       if ok {
+               // no error expected
+               if err != nil {
+                       log.Fatalf("%s: compile failed unexpectedly: %v", name, err)
+               }
+               return
+       }
+
+       // error expected
+       if err == nil {
+               log.Fatalf("%s: compile succeeded unexpectedly", name)
+       }
+       if !bytes.Contains(output, []byte("excessively long constant")) {
+               log.Fatalf("%s: wrong compiler error message:\n%s\n", name, output)
+       }
+}
+
+func main() {
+       if runtime.GOOS == "js" || runtime.Compiler != "gc" {
+               return
+       }
+
+       dir, err := ioutil.TempDir("", "const7_")
+       if err != nil {
+               log.Fatalf("creating temp dir: %v\n", err)
+       }
+       defer os.RemoveAll(dir)
+
+       const limit = 10000 // compiler-internal constant length limit
+       testProg(dir, "x1", 0, limit, true)    // -G=0
+       testProg(dir, "x2", 0, limit+1, false) // -G=0
+       testProg(dir, "x1", 1, limit, true)    // -G=1 (new type checker)
+       testProg(dir, "x2", 1, limit+1, false) // -G=1 (new type checker)
+}