aboutsummaryrefslogtreecommitdiff
path: root/libgo
diff options
context:
space:
mode:
authorIan Lance Taylor <ian@gcc.gnu.org>2019-08-28 18:27:30 +0000
committerIan Lance Taylor <ian@gcc.gnu.org>2019-08-28 18:27:30 +0000
commitfc4f90f0c8eca75fb90c736476360584f68d7ef9 (patch)
treeff64bd8a2576d7eb0b85638a14cc3a761cb72db1 /libgo
parent464969eb9b47eb2f24403c74c16769a58dbaa638 (diff)
downloadgcc-fc4f90f0c8eca75fb90c736476360584f68d7ef9.zip
gcc-fc4f90f0c8eca75fb90c736476360584f68d7ef9.tar.gz
gcc-fc4f90f0c8eca75fb90c736476360584f68d7ef9.tar.bz2
compiler, runtime: provide index information on bounds check failure
This implements https://golang.org/cl/161477 in the gofrontend. Updates golang/go#30116 Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/191881 From-SVN: r274998
Diffstat (limited to 'libgo')
-rw-r--r--libgo/go/runtime/error.go108
-rw-r--r--libgo/go/runtime/panic.go170
-rw-r--r--libgo/go/runtime/panic32.go108
-rwxr-xr-xlibgo/mkruntimeinc.sh5
4 files changed, 358 insertions, 33 deletions
diff --git a/libgo/go/runtime/error.go b/libgo/go/runtime/error.go
index b1a3f68..0c7f631 100644
--- a/libgo/go/runtime/error.go
+++ b/libgo/go/runtime/error.go
@@ -79,6 +79,21 @@ func unquote(s string) string {
return string(r[:j])
}
+//go:nosplit
+// itoa converts val to a decimal representation. The result is
+// written somewhere within buf and the location of the result is returned.
+// buf must be at least 20 bytes.
+func itoa(buf []byte, val uint64) []byte {
+ i := len(buf) - 1
+ for val >= 10 {
+ buf[i] = byte(val%10 + '0')
+ i--
+ val /= 10
+ }
+ buf[i] = byte(val + '0')
+ return buf[i:]
+}
+
// An errorString represents a runtime error described by a single string.
type errorString string
@@ -114,6 +129,99 @@ func (e plainError) Error() string {
return string(e)
}
+// An boundsError represents a an indexing or slicing operation gone wrong.
+type boundsError struct {
+ x int64
+ y int
+ // Values in an index or slice expression can be signed or unsigned.
+ // That means we'd need 65 bits to encode all possible indexes, from -2^63 to 2^64-1.
+ // Instead, we keep track of whether x should be interpreted as signed or unsigned.
+ // y is known to be nonnegative and to fit in an int.
+ signed bool
+ code boundsErrorCode
+}
+
+type boundsErrorCode uint8
+
+const (
+ boundsIndex boundsErrorCode = iota // s[x], 0 <= x < len(s) failed
+
+ boundsSliceAlen // s[?:x], 0 <= x <= len(s) failed
+ boundsSliceAcap // s[?:x], 0 <= x <= cap(s) failed
+ boundsSliceB // s[x:y], 0 <= x <= y failed (but boundsSliceA didn't happen)
+
+ boundsSlice3Alen // s[?:?:x], 0 <= x <= len(s) failed
+ boundsSlice3Acap // s[?:?:x], 0 <= x <= cap(s) failed
+ boundsSlice3B // s[?:x:y], 0 <= x <= y failed (but boundsSlice3A didn't happen)
+ boundsSlice3C // s[x:y:?], 0 <= x <= y failed (but boundsSlice3A/B didn't happen)
+
+ // Note: in the above, len(s) and cap(s) are stored in y
+)
+
+// boundsErrorFmts provide error text for various out-of-bounds panics.
+// Note: if you change these strings, you should adjust the size of the buffer
+// in boundsError.Error below as well.
+var boundsErrorFmts = [...]string{
+ boundsIndex: "index out of range [%x] with length %y",
+ boundsSliceAlen: "slice bounds out of range [:%x] with length %y",
+ boundsSliceAcap: "slice bounds out of range [:%x] with capacity %y",
+ boundsSliceB: "slice bounds out of range [%x:%y]",
+ boundsSlice3Alen: "slice bounds out of range [::%x] with length %y",
+ boundsSlice3Acap: "slice bounds out of range [::%x] with capacity %y",
+ boundsSlice3B: "slice bounds out of range [:%x:%y]",
+ boundsSlice3C: "slice bounds out of range [%x:%y:]",
+}
+
+// boundsNegErrorFmts are overriding formats if x is negative. In this case there's no need to report y.
+var boundsNegErrorFmts = [...]string{
+ boundsIndex: "index out of range [%x]",
+ boundsSliceAlen: "slice bounds out of range [:%x]",
+ boundsSliceAcap: "slice bounds out of range [:%x]",
+ boundsSliceB: "slice bounds out of range [%x:]",
+ boundsSlice3Alen: "slice bounds out of range [::%x]",
+ boundsSlice3Acap: "slice bounds out of range [::%x]",
+ boundsSlice3B: "slice bounds out of range [:%x:]",
+ boundsSlice3C: "slice bounds out of range [%x::]",
+}
+
+func (e boundsError) RuntimeError() {}
+
+func appendIntStr(b []byte, v int64, signed bool) []byte {
+ if signed && v < 0 {
+ b = append(b, '-')
+ v = -v
+ }
+ var buf [20]byte
+ b = append(b, itoa(buf[:], uint64(v))...)
+ return b
+}
+
+func (e boundsError) Error() string {
+ fmt := boundsErrorFmts[e.code]
+ if e.signed && e.x < 0 {
+ fmt = boundsNegErrorFmts[e.code]
+ }
+ // max message length is 99: "runtime error: slice bounds out of range [::%x] with capacity %y"
+ // x can be at most 20 characters. y can be at most 19.
+ b := make([]byte, 0, 100)
+ b = append(b, "runtime error: "...)
+ for i := 0; i < len(fmt); i++ {
+ c := fmt[i]
+ if c != '%' {
+ b = append(b, c)
+ continue
+ }
+ i++
+ switch fmt[i] {
+ case 'x':
+ b = appendIntStr(b, e.x, e.signed)
+ case 'y':
+ b = appendIntStr(b, int64(e.y), true)
+ }
+ }
+ return string(b)
+}
+
type stringer interface {
String() string
}
diff --git a/libgo/go/runtime/panic.go b/libgo/go/runtime/panic.go
index 88c0a4d..5868430 100644
--- a/libgo/go/runtime/panic.go
+++ b/libgo/go/runtime/panic.go
@@ -23,81 +23,189 @@ import (
//go:linkname makefuncreturning runtime.makefuncreturning
//go:linkname gorecover runtime.gorecover
//go:linkname deferredrecover runtime.deferredrecover
+//go:linkname goPanicIndex runtime.goPanicIndex
+//go:linkname goPanicIndexU runtime.goPanicIndexU
+//go:linkname goPanicSliceAlen runtime.goPanicSliceAlen
+//go:linkname goPanicSliceAlenU runtime.goPanicSliceAlenU
+//go:linkname goPanicSliceAcap runtime.goPanicSliceAcap
+//go:linkname goPanicSliceAcapU runtime.goPanicSliceAcapU
+//go:linkname goPanicSliceB runtime.goPanicSliceB
+//go:linkname goPanicSliceBU runtime.goPanicSliceBU
+//go:linkname goPanicSlice3Alen runtime.goPanicSlice3Alen
+//go:linkname goPanicSlice3AlenU runtime.goPanicSlice3AlenU
+//go:linkname goPanicSlice3Acap runtime.goPanicSlice3Acap
+//go:linkname goPanicSlice3AcapU runtime.goPanicSlice3AcapU
+//go:linkname goPanicSlice3B runtime.goPanicSlice3B
+//go:linkname goPanicSlice3BU runtime.goPanicSlice3BU
+//go:linkname goPanicSlice3C runtime.goPanicSlice3C
+//go:linkname goPanicSlice3CU runtime.goPanicSlice3CU
//go:linkname panicmem runtime.panicmem
// Temporary for C code to call:
//go:linkname throw runtime.throw
-// Calling panic with one of the errors below will call errorString.Error
-// which will call mallocgc to concatenate strings. That will fail if
-// malloc is locked, causing a confusing error message. Throw a better
-// error message instead.
-func panicCheckMalloc(err error) {
+// Check to make sure we can really generate a panic. If the panic
+// was generated from the runtime, or from inside malloc, then convert
+// to a throw of msg.
+// pc should be the program counter of the compiler-generated code that
+// triggered this panic.
+func panicCheck1(pc uintptr, msg string) {
+ name, _, _, _ := funcfileline(pc-1, -1)
+ if hasPrefix(name, "runtime.") {
+ throw(msg)
+ }
+ // TODO: is this redundant? How could we be in malloc
+ // but not in the runtime? runtime/internal/*, maybe?
gp := getg()
if gp != nil && gp.m != nil && gp.m.mallocing != 0 {
- throw(string(err.(errorString)))
+ throw(msg)
}
}
-var indexError = error(errorString("index out of range"))
+// Same as above, but calling from the runtime is allowed.
+//
+// Using this function is necessary for any panic that may be
+// generated by runtime.sigpanic, since those are always called by the
+// runtime.
+func panicCheck2(err string) {
+ // panic allocates, so to avoid recursive malloc, turn panics
+ // during malloc into throws.
+ gp := getg()
+ if gp != nil && gp.m != nil && gp.m.mallocing != 0 {
+ throw(err)
+ }
+}
-// The panicindex, panicslice, and panicdivide functions are called by
+// Many of the following panic entry-points turn into throws when they
+// happen in various runtime contexts. These should never happen in
+// the runtime, and if they do, they indicate a serious issue and
+// should not be caught by user code.
+//
+// The panic{Index,Slice,divide,shift} functions are called by
// code generated by the compiler for out of bounds index expressions,
-// out of bounds slice expressions, and division by zero. The
-// panicdivide (again), panicoverflow, panicfloat, and panicmem
+// out of bounds slice expressions, division by zero, and shift by negative.
+// The panicdivide (again), panicoverflow, panicfloat, and panicmem
// functions are called by the signal handler when a signal occurs
// indicating the respective problem.
//
-// Since panicindex and panicslice are never called directly, and
+// Since panic{Index,Slice,shift} are never called directly, and
// since the runtime package should never have an out of bounds slice
-// or array reference, if we see those functions called from the
+// or array reference or negative shift, if we see those functions called from the
// runtime package we turn the panic into a throw. That will dump the
// entire runtime stack for easier debugging.
+//
+// The entry points called by the signal handler will be called from
+// runtime.sigpanic, so we can't disallow calls from the runtime to
+// these (they always look like they're called from the runtime).
+// Hence, for these, we just check for clearly bad runtime conditions.
+
+// failures in the comparisons for s[x], 0 <= x < y (y == len(s))
+func goPanicIndex(x int, y int) {
+ panicCheck1(getcallerpc(), "index out of range")
+ panic(boundsError{x: int64(x), signed: true, y: y, code: boundsIndex})
+}
+func goPanicIndexU(x uint, y int) {
+ panicCheck1(getcallerpc(), "index out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsIndex})
+}
-func panicindex() {
- name, _, _, _ := funcfileline(getcallerpc()-1, -1)
- if hasPrefix(name, "runtime.") {
- throw(string(indexError.(errorString)))
- }
- panicCheckMalloc(indexError)
- panic(indexError)
+// failures in the comparisons for s[:x], 0 <= x <= y (y == len(s) or cap(s))
+func goPanicSliceAlen(x int, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: true, y: y, code: boundsSliceAlen})
+}
+func goPanicSliceAlenU(x uint, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsSliceAlen})
+}
+func goPanicSliceAcap(x int, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: true, y: y, code: boundsSliceAcap})
+}
+func goPanicSliceAcapU(x uint, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsSliceAcap})
}
-var sliceError = error(errorString("slice bounds out of range"))
+// failures in the comparisons for s[x:y], 0 <= x <= y
+func goPanicSliceB(x int, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: true, y: y, code: boundsSliceB})
+}
+func goPanicSliceBU(x uint, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsSliceB})
+}
-func panicslice() {
- name, _, _, _ := funcfileline(getcallerpc()-1, -1)
- if hasPrefix(name, "runtime.") {
- throw(string(sliceError.(errorString)))
- }
- panicCheckMalloc(sliceError)
- panic(sliceError)
+// failures in the comparisons for s[::x], 0 <= x <= y (y == len(s) or cap(s))
+func goPanicSlice3Alen(x int, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: true, y: y, code: boundsSlice3Alen})
+}
+func goPanicSlice3AlenU(x uint, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsSlice3Alen})
+}
+func goPanicSlice3Acap(x int, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: true, y: y, code: boundsSlice3Acap})
+}
+func goPanicSlice3AcapU(x uint, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsSlice3Acap})
+}
+
+// failures in the comparisons for s[:x:y], 0 <= x <= y
+func goPanicSlice3B(x int, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: true, y: y, code: boundsSlice3B})
+}
+func goPanicSlice3BU(x uint, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsSlice3B})
+}
+
+// failures in the comparisons for s[x:y:], 0 <= x <= y
+func goPanicSlice3C(x int, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: true, y: y, code: boundsSlice3C})
+}
+func goPanicSlice3CU(x uint, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsSlice3C})
+}
+
+var shiftError = error(errorString("negative shift amount"))
+
+func panicshift() {
+ panicCheck1(getcallerpc(), "negative shift amount")
+ panic(shiftError)
}
var divideError = error(errorString("integer divide by zero"))
func panicdivide() {
- panicCheckMalloc(divideError)
+ panicCheck2("integer divide by zero")
panic(divideError)
}
var overflowError = error(errorString("integer overflow"))
func panicoverflow() {
- panicCheckMalloc(overflowError)
+ panicCheck2("integer overflow")
panic(overflowError)
}
var floatError = error(errorString("floating point error"))
func panicfloat() {
- panicCheckMalloc(floatError)
+ panicCheck2("floating point error")
panic(floatError)
}
var memoryError = error(errorString("invalid memory address or nil pointer dereference"))
func panicmem() {
- panicCheckMalloc(memoryError)
+ panicCheck2("invalid memory address or nil pointer dereference")
panic(memoryError)
}
diff --git a/libgo/go/runtime/panic32.go b/libgo/go/runtime/panic32.go
new file mode 100644
index 0000000..f68d4c7
--- /dev/null
+++ b/libgo/go/runtime/panic32.go
@@ -0,0 +1,108 @@
+// Copyright 2019 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.
+
+// +build 386 amd64p32 arm mips mipsle m68k nios2 sh shbe
+
+package runtime
+
+import _ "unsafe" // for go:linkname
+
+// For gccgo, use go:linkname to rename compiler-called functions to
+// themselves, so that the compiler will export them.
+//
+//go:linkname goPanicExtendIndex runtime.goPanicExtendIndex
+//go:linkname goPanicExtendIndexU runtime.goPanicExtendIndexU
+//go:linkname goPanicExtendSliceAlen runtime.goPanicExtendSliceAlen
+//go:linkname goPanicExtendSliceAlenU runtime.goPanicExtendSliceAlenU
+//go:linkname goPanicExtendSliceAcap runtime.goPanicExtendSliceAcap
+//go:linkname goPanicExtendSliceAcapU runtime.goPanicExtendSliceAcapU
+//go:linkname goPanicExtendSliceB runtime.goPanicExtendSliceB
+//go:linkname goPanicExtendSliceBU runtime.goPanicExtendSliceBU
+//go:linkname goPanicExtendSlice3Alen runtime.goPanicExtendSlice3Alen
+//go:linkname goPanicExtendSlice3AlenU runtime.goPanicExtendSlice3AlenU
+//go:linkname goPanicExtendSlice3Acap runtime.goPanicExtendSlice3Acap
+//go:linkname goPanicExtendSlice3AcapU runtime.goPanicExtendSlice3AcapU
+//go:linkname goPanicExtendSlice3B runtime.goPanicExtendSlice3B
+//go:linkname goPanicExtendSlice3BU runtime.goPanicExtendSlice3BU
+//go:linkname goPanicExtendSlice3C runtime.goPanicExtendSlice3C
+//go:linkname goPanicExtendSlice3CU runtime.goPanicExtendSlice3CU
+
+// Additional index/slice error paths for 32-bit platforms.
+// Used when the high word of a 64-bit index is not zero.
+
+// failures in the comparisons for s[x], 0 <= x < y (y == len(s))
+func goPanicExtendIndex(x int64, y int) {
+ panicCheck1(getcallerpc(), "index out of range")
+ panic(boundsError{x: x, signed: true, y: y, code: boundsIndex})
+}
+func goPanicExtendIndexU(x uint64, y int) {
+ panicCheck1(getcallerpc(), "index out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsIndex})
+}
+
+// failures in the comparisons for s[:x], 0 <= x <= y (y == len(s) or cap(s))
+func goPanicExtendSliceAlen(x int64, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: x, signed: true, y: y, code: boundsSliceAlen})
+}
+func goPanicExtendSliceAlenU(x uint64, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsSliceAlen})
+}
+func goPanicExtendSliceAcap(x int64, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: x, signed: true, y: y, code: boundsSliceAcap})
+}
+func goPanicExtendSliceAcapU(x uint64, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsSliceAcap})
+}
+
+// failures in the comparisons for s[x:y], 0 <= x <= y
+func goPanicExtendSliceB(x int64, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: x, signed: true, y: y, code: boundsSliceB})
+}
+func goPanicExtendSliceBU(x uint64, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsSliceB})
+}
+
+// failures in the comparisons for s[::x], 0 <= x <= y (y == len(s) or cap(s))
+func goPanicExtendSlice3Alen(x int64, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: x, signed: true, y: y, code: boundsSlice3Alen})
+}
+func goPanicExtendSlice3AlenU(x uint64, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsSlice3Alen})
+}
+func goPanicExtendSlice3Acap(x int64, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: x, signed: true, y: y, code: boundsSlice3Acap})
+}
+func goPanicExtendSlice3AcapU(x uint64, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsSlice3Acap})
+}
+
+// failures in the comparisons for s[:x:y], 0 <= x <= y
+func goPanicExtendSlice3B(x int64, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: x, signed: true, y: y, code: boundsSlice3B})
+}
+func goPanicExtendSlice3BU(x uint64, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsSlice3B})
+}
+
+// failures in the comparisons for s[x:y:], 0 <= x <= y
+func goPanicExtendSlice3C(x int64, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: x, signed: true, y: y, code: boundsSlice3C})
+}
+func goPanicExtendSlice3CU(x uint64, y int) {
+ panicCheck1(getcallerpc(), "slice bounds out of range")
+ panic(boundsError{x: int64(x), signed: false, y: y, code: boundsSlice3C})
+}
diff --git a/libgo/mkruntimeinc.sh b/libgo/mkruntimeinc.sh
index cd95595..d29da9b 100755
--- a/libgo/mkruntimeinc.sh
+++ b/libgo/mkruntimeinc.sh
@@ -15,14 +15,15 @@ rm -f runtime.inc.tmp2 runtime.inc.tmp3
# types and should not be exported back to C
# semt is a Go translation of the C type sem_t; it fails to convert on
# some systems and need not be exported back to C.
-# sigset conflicts with system type sigset on AIX, so we need to rename it
+# sigset conflicts with system type sigset on AIX, so we need to rename it.
+# boundsError has a field name that is a C keyword, and we don't need it.
grep -v "#define _" ${IN} | grep -v "#define [cm][01234] " | grep -v "#define empty " | grep -v "#define \\$" > runtime.inc.tmp2
for pattern in '_[GP][a-z]' _Max _Lock _Sig _Trace _MHeap _Num
do
grep "#define $pattern" ${IN} >> runtime.inc.tmp2
done
-TYPES="_Complex_lock _Reader_lock semt"
+TYPES="_Complex_lock _Reader_lock semt boundsError"
for TYPE in $TYPES
do
sed -e '/struct '${TYPE}' {/,/^}/s/^.*$//' runtime.inc.tmp2 > runtime.inc.tmp3;