diff options
author | Ian Lance Taylor <ian@gcc.gnu.org> | 2019-08-28 18:27:30 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2019-08-28 18:27:30 +0000 |
commit | fc4f90f0c8eca75fb90c736476360584f68d7ef9 (patch) | |
tree | ff64bd8a2576d7eb0b85638a14cc3a761cb72db1 /libgo | |
parent | 464969eb9b47eb2f24403c74c16769a58dbaa638 (diff) | |
download | gcc-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.go | 108 | ||||
-rw-r--r-- | libgo/go/runtime/panic.go | 170 | ||||
-rw-r--r-- | libgo/go/runtime/panic32.go | 108 | ||||
-rwxr-xr-x | libgo/mkruntimeinc.sh | 5 |
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; |