mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
cmd/compile,runtime: change unsafe.Slice((*T)(nil), 0) to return []T(nil)
This CL removes the unconditional OCHECKNIL check added in walkUnsafeSlice by instead passing it as a pointer to runtime.unsafeslice, and hiding the check behind a `len == 0` check. While here, this CL also implements checkptr functionality for unsafe.Slice and disallows use of unsafe.Slice with //go:notinheap types. Updates #46742. Change-Id: I743a445ac124304a4d7322a7fe089c4a21b9a655 Reviewed-on: https://go-review.googlesource.com/c/go/+/331070 Run-TryBot: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Go Bot <gobot@golang.org> Trust: Matthew Dempsky <mdempsky@google.com> Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
parent
1519271a93
commit
4bb0847b08
9 changed files with 83 additions and 27 deletions
|
|
@ -138,6 +138,7 @@ var runtimeDecls = [...]struct {
|
||||||
{"growslice", funcTag, 116},
|
{"growslice", funcTag, 116},
|
||||||
{"unsafeslice", funcTag, 117},
|
{"unsafeslice", funcTag, 117},
|
||||||
{"unsafeslice64", funcTag, 118},
|
{"unsafeslice64", funcTag, 118},
|
||||||
|
{"unsafeslicecheckptr", funcTag, 118},
|
||||||
{"memmove", funcTag, 119},
|
{"memmove", funcTag, 119},
|
||||||
{"memclrNoHeapPointers", funcTag, 120},
|
{"memclrNoHeapPointers", funcTag, 120},
|
||||||
{"memclrHasPointers", funcTag, 120},
|
{"memclrHasPointers", funcTag, 120},
|
||||||
|
|
@ -341,8 +342,8 @@ func runtimeTypes() []*types.Type {
|
||||||
typs[114] = newSig(params(typs[1], typs[15], typs[15], typs[7]), params(typs[7]))
|
typs[114] = newSig(params(typs[1], typs[15], typs[15], typs[7]), params(typs[7]))
|
||||||
typs[115] = types.NewSlice(typs[2])
|
typs[115] = types.NewSlice(typs[2])
|
||||||
typs[116] = newSig(params(typs[1], typs[115], typs[15]), params(typs[115]))
|
typs[116] = newSig(params(typs[1], typs[115], typs[15]), params(typs[115]))
|
||||||
typs[117] = newSig(params(typs[1], typs[15]), nil)
|
typs[117] = newSig(params(typs[1], typs[7], typs[15]), nil)
|
||||||
typs[118] = newSig(params(typs[1], typs[22]), nil)
|
typs[118] = newSig(params(typs[1], typs[7], typs[22]), nil)
|
||||||
typs[119] = newSig(params(typs[3], typs[3], typs[5]), nil)
|
typs[119] = newSig(params(typs[3], typs[3], typs[5]), nil)
|
||||||
typs[120] = newSig(params(typs[7], typs[5]), nil)
|
typs[120] = newSig(params(typs[7], typs[5]), nil)
|
||||||
typs[121] = newSig(params(typs[3], typs[3], typs[5]), params(typs[6]))
|
typs[121] = newSig(params(typs[3], typs[3], typs[5]), params(typs[6]))
|
||||||
|
|
|
||||||
|
|
@ -183,8 +183,9 @@ func makeslice(typ *byte, len int, cap int) unsafe.Pointer
|
||||||
func makeslice64(typ *byte, len int64, cap int64) unsafe.Pointer
|
func makeslice64(typ *byte, len int64, cap int64) unsafe.Pointer
|
||||||
func makeslicecopy(typ *byte, tolen int, fromlen int, from unsafe.Pointer) unsafe.Pointer
|
func makeslicecopy(typ *byte, tolen int, fromlen int, from unsafe.Pointer) unsafe.Pointer
|
||||||
func growslice(typ *byte, old []any, cap int) (ary []any)
|
func growslice(typ *byte, old []any, cap int) (ary []any)
|
||||||
func unsafeslice(typ *byte, len int)
|
func unsafeslice(typ *byte, ptr unsafe.Pointer, len int)
|
||||||
func unsafeslice64(typ *byte, len int64)
|
func unsafeslice64(typ *byte, ptr unsafe.Pointer, len int64)
|
||||||
|
func unsafeslicecheckptr(typ *byte, ptr unsafe.Pointer, len int64)
|
||||||
|
|
||||||
func memmove(to *any, frm *any, length uintptr)
|
func memmove(to *any, frm *any, length uintptr)
|
||||||
func memclrNoHeapPointers(ptr unsafe.Pointer, n uintptr)
|
func memclrNoHeapPointers(ptr unsafe.Pointer, n uintptr)
|
||||||
|
|
|
||||||
|
|
@ -1018,7 +1018,14 @@ func tcUnsafeSlice(n *ir.BinaryExpr) *ir.BinaryExpr {
|
||||||
t := n.X.Type()
|
t := n.X.Type()
|
||||||
if !t.IsPtr() {
|
if !t.IsPtr() {
|
||||||
base.Errorf("first argument to unsafe.Slice must be pointer; have %L", t)
|
base.Errorf("first argument to unsafe.Slice must be pointer; have %L", t)
|
||||||
|
} else if t.Elem().NotInHeap() {
|
||||||
|
// TODO(mdempsky): This can be relaxed, but should only affect the
|
||||||
|
// Go runtime itself. End users should only see //go:notinheap
|
||||||
|
// types due to incomplete C structs in cgo, and those types don't
|
||||||
|
// have a meaningful size anyway.
|
||||||
|
base.Errorf("unsafe.Slice of incomplete (or unallocatable) type not allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !checkunsafeslice(&n.Y) {
|
if !checkunsafeslice(&n.Y) {
|
||||||
n.SetType(nil)
|
n.SetType(nil)
|
||||||
return n
|
return n
|
||||||
|
|
|
||||||
|
|
@ -654,36 +654,28 @@ func walkRecover(nn *ir.CallExpr, init *ir.Nodes) ir.Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
func walkUnsafeSlice(n *ir.BinaryExpr, init *ir.Nodes) ir.Node {
|
func walkUnsafeSlice(n *ir.BinaryExpr, init *ir.Nodes) ir.Node {
|
||||||
|
ptr := safeExpr(n.X, init)
|
||||||
len := safeExpr(n.Y, init)
|
len := safeExpr(n.Y, init)
|
||||||
|
|
||||||
fnname := "unsafeslice64"
|
fnname := "unsafeslice64"
|
||||||
argtype := types.Types[types.TINT64]
|
lenType := types.Types[types.TINT64]
|
||||||
|
|
||||||
// Type checking guarantees that TIDEAL len/cap are positive and fit in an int.
|
// Type checking guarantees that TIDEAL len/cap are positive and fit in an int.
|
||||||
// The case of len or cap overflow when converting TUINT or TUINTPTR to TINT
|
// The case of len or cap overflow when converting TUINT or TUINTPTR to TINT
|
||||||
// will be handled by the negative range checks in unsafeslice during runtime.
|
// will be handled by the negative range checks in unsafeslice during runtime.
|
||||||
if len.Type().IsKind(types.TIDEAL) || len.Type().Size() <= types.Types[types.TUINT].Size() {
|
if ir.ShouldCheckPtr(ir.CurFunc, 1) {
|
||||||
|
fnname = "unsafeslicecheckptr"
|
||||||
|
// for simplicity, unsafeslicecheckptr always uses int64
|
||||||
|
} else if len.Type().IsKind(types.TIDEAL) || len.Type().Size() <= types.Types[types.TUINT].Size() {
|
||||||
fnname = "unsafeslice"
|
fnname = "unsafeslice"
|
||||||
argtype = types.Types[types.TINT]
|
lenType = types.Types[types.TINT]
|
||||||
}
|
}
|
||||||
|
|
||||||
t := n.Type()
|
t := n.Type()
|
||||||
|
|
||||||
// Call runtime.unsafeslice[64] to check that the length argument is
|
// Call runtime.unsafeslice{,64,checkptr} to check ptr and len.
|
||||||
// non-negative and smaller than the max length allowed for the
|
|
||||||
// element type.
|
|
||||||
fn := typecheck.LookupRuntime(fnname)
|
fn := typecheck.LookupRuntime(fnname)
|
||||||
init.Append(mkcall1(fn, nil, init, reflectdata.TypePtr(t.Elem()), typecheck.Conv(len, argtype)))
|
init.Append(mkcall1(fn, nil, init, reflectdata.TypePtr(t.Elem()), typecheck.Conv(ptr, types.Types[types.TUNSAFEPTR]), typecheck.Conv(len, lenType)))
|
||||||
|
|
||||||
ptr := walkExpr(n.X, init)
|
|
||||||
|
|
||||||
c := ir.NewUnaryExpr(n.Pos(), ir.OCHECKNIL, ptr)
|
|
||||||
c.SetTypecheck(1)
|
|
||||||
init.Append(c)
|
|
||||||
|
|
||||||
// TODO(mdempsky): checkptr instrumentation. Maybe merge into length
|
|
||||||
// check above, along with nil check? Need to be careful about
|
|
||||||
// notinheap pointers though: can't pass them as unsafe.Pointer.
|
|
||||||
|
|
||||||
h := ir.NewSliceHeaderExpr(n.Pos(), t,
|
h := ir.NewSliceHeaderExpr(n.Pos(), t,
|
||||||
typecheck.Conv(ptr, types.Types[types.TUNSAFEPTR]),
|
typecheck.Conv(ptr, types.Types[types.TUNSAFEPTR]),
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,30 @@ func checkptrAlignment(p unsafe.Pointer, elem *_type, n uintptr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that (*[n]elem)(p) doesn't straddle multiple heap objects.
|
// Check that (*[n]elem)(p) doesn't straddle multiple heap objects.
|
||||||
if size := n * elem.size; size > 1 && checkptrBase(p) != checkptrBase(add(p, size-1)) {
|
// TODO(mdempsky): Fix #46938 so we don't need to worry about overflow here.
|
||||||
|
if checkptrStraddles(p, n*elem.size) {
|
||||||
throw("checkptr: converted pointer straddles multiple allocations")
|
throw("checkptr: converted pointer straddles multiple allocations")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkptrStraddles reports whether the first size-bytes of memory
|
||||||
|
// addressed by ptr is known to straddle more than one Go allocation.
|
||||||
|
func checkptrStraddles(ptr unsafe.Pointer, size uintptr) bool {
|
||||||
|
if size <= 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
end := add(ptr, size-1)
|
||||||
|
if uintptr(end) < uintptr(ptr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(mdempsky): Detect when [ptr, end] contains Go allocations,
|
||||||
|
// but neither ptr nor end point into one themselves.
|
||||||
|
|
||||||
|
return checkptrBase(ptr) != checkptrBase(end)
|
||||||
|
}
|
||||||
|
|
||||||
func checkptrArithmetic(p unsafe.Pointer, originals []unsafe.Pointer) {
|
func checkptrArithmetic(p unsafe.Pointer, originals []unsafe.Pointer) {
|
||||||
if 0 < uintptr(p) && uintptr(p) < minLegalPointer {
|
if 0 < uintptr(p) && uintptr(p) < minLegalPointer {
|
||||||
throw("checkptr: pointer arithmetic computed bad pointer value")
|
throw("checkptr: pointer arithmetic computed bad pointer value")
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ func TestCheckPtr(t *testing.T) {
|
||||||
{"CheckPtrArithmetic2", "fatal error: checkptr: pointer arithmetic result points to invalid allocation\n"},
|
{"CheckPtrArithmetic2", "fatal error: checkptr: pointer arithmetic result points to invalid allocation\n"},
|
||||||
{"CheckPtrSize", "fatal error: checkptr: converted pointer straddles multiple allocations\n"},
|
{"CheckPtrSize", "fatal error: checkptr: converted pointer straddles multiple allocations\n"},
|
||||||
{"CheckPtrSmall", "fatal error: checkptr: pointer arithmetic computed bad pointer value\n"},
|
{"CheckPtrSmall", "fatal error: checkptr: pointer arithmetic computed bad pointer value\n"},
|
||||||
|
{"CheckPtrSliceOK", ""},
|
||||||
|
{"CheckPtrSliceFail", "fatal error: checkptr: unsafe.Slice result straddles multiple allocations\n"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|
|
||||||
|
|
@ -112,19 +112,37 @@ func makeslice64(et *_type, len64, cap64 int64) unsafe.Pointer {
|
||||||
return makeslice(et, len, cap)
|
return makeslice(et, len, cap)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unsafeslice(et *_type, len int) {
|
func unsafeslice(et *_type, ptr unsafe.Pointer, len int) {
|
||||||
|
if len == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ptr == nil {
|
||||||
|
panic(errorString("unsafe.Slice: ptr is nil and len is not zero"))
|
||||||
|
}
|
||||||
|
|
||||||
mem, overflow := math.MulUintptr(et.size, uintptr(len))
|
mem, overflow := math.MulUintptr(et.size, uintptr(len))
|
||||||
if overflow || mem > maxAlloc || len < 0 {
|
if overflow || mem > maxAlloc || len < 0 {
|
||||||
panicunsafeslicelen()
|
panicunsafeslicelen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func unsafeslice64(et *_type, len64 int64) {
|
func unsafeslice64(et *_type, ptr unsafe.Pointer, len64 int64) {
|
||||||
len := int(len64)
|
len := int(len64)
|
||||||
if int64(len) != len64 {
|
if int64(len) != len64 {
|
||||||
panicunsafeslicelen()
|
panicunsafeslicelen()
|
||||||
}
|
}
|
||||||
unsafeslice(et, len)
|
unsafeslice(et, ptr, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsafeslicecheckptr(et *_type, ptr unsafe.Pointer, len64 int64) {
|
||||||
|
unsafeslice64(et, ptr, len64)
|
||||||
|
|
||||||
|
// Check that underlying array doesn't straddle multiple heap objects.
|
||||||
|
// unsafeslice64 has already checked for overflow.
|
||||||
|
if checkptrStraddles(ptr, uintptr(len64)*et.size) {
|
||||||
|
throw("checkptr: unsafe.Slice result straddles multiple allocations")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func panicunsafeslicelen() {
|
func panicunsafeslicelen() {
|
||||||
|
|
|
||||||
13
src/runtime/testdata/testprog/checkptr.go
vendored
13
src/runtime/testdata/testprog/checkptr.go
vendored
|
|
@ -13,6 +13,8 @@ func init() {
|
||||||
register("CheckPtrArithmetic2", CheckPtrArithmetic2)
|
register("CheckPtrArithmetic2", CheckPtrArithmetic2)
|
||||||
register("CheckPtrSize", CheckPtrSize)
|
register("CheckPtrSize", CheckPtrSize)
|
||||||
register("CheckPtrSmall", CheckPtrSmall)
|
register("CheckPtrSmall", CheckPtrSmall)
|
||||||
|
register("CheckPtrSliceOK", CheckPtrSliceOK)
|
||||||
|
register("CheckPtrSliceFail", CheckPtrSliceFail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckPtrAlignmentNoPtr() {
|
func CheckPtrAlignmentNoPtr() {
|
||||||
|
|
@ -49,3 +51,14 @@ func CheckPtrSize() {
|
||||||
func CheckPtrSmall() {
|
func CheckPtrSmall() {
|
||||||
sink2 = unsafe.Pointer(uintptr(1))
|
sink2 = unsafe.Pointer(uintptr(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckPtrSliceOK() {
|
||||||
|
p := new([4]int64)
|
||||||
|
sink2 = unsafe.Slice(&p[1], 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckPtrSliceFail() {
|
||||||
|
p := new(int64)
|
||||||
|
sink2 = p
|
||||||
|
sink2 = unsafe.Slice(p, 100)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,11 @@ func main() {
|
||||||
assert(len(s) == len(p))
|
assert(len(s) == len(p))
|
||||||
assert(cap(s) == len(p))
|
assert(cap(s) == len(p))
|
||||||
|
|
||||||
// nil pointer
|
// nil pointer with zero length returns nil
|
||||||
mustPanic(func() { _ = unsafe.Slice((*int)(nil), 0) })
|
assert(unsafe.Slice((*int)(nil), 0) == nil)
|
||||||
|
|
||||||
|
// nil pointer with positive length panics
|
||||||
|
mustPanic(func() { _ = unsafe.Slice((*int)(nil), 1) })
|
||||||
|
|
||||||
// negative length
|
// negative length
|
||||||
var neg int = -1
|
var neg int = -1
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue